codemp/game/NPC_AI_Rancor.c

Go to the documentation of this file.
00001 // leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
00002 #include "g_headers.h"
00003 
00004             
00005 #include "b_local.h"
00006 
00007 extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex );
00008 
00009 // These define the working combat range for these suckers
00010 #define MIN_DISTANCE            128
00011 #define MIN_DISTANCE_SQR        ( MIN_DISTANCE * MIN_DISTANCE )
00012 
00013 #define MAX_DISTANCE            1024
00014 #define MAX_DISTANCE_SQR        ( MAX_DISTANCE * MAX_DISTANCE )
00015 
00016 #define LSTATE_CLEAR            0
00017 #define LSTATE_WAITING          1
00018 
00019 void Rancor_SetBolts( gentity_t *self )
00020 {
00021         if ( self && self->client )
00022         {
00023                 renderInfo_t *ri = &self->client->renderInfo;
00024                 ri->handRBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*r_hand" );
00025                 ri->handLBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_hand" );
00026                 ri->headBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*head_eyes" );
00027                 ri->torsoBolt = trap_G2API_AddBolt( self->ghoul2, 0, "jaw_bone" );
00028         }
00029 }
00030 
00031 /*
00032 -------------------------
00033 NPC_Rancor_Precache
00034 -------------------------
00035 */
00036 void NPC_Rancor_Precache( void )
00037 {
00038         int i;
00039         for ( i = 1; i < 3; i ++ )
00040         {
00041                 G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", i) );
00042         }
00043         G_SoundIndex( "sound/chars/rancor/swipehit.wav" );
00044         G_SoundIndex( "sound/chars/rancor/chomp.wav" );
00045 }
00046 
00047 
00048 /*
00049 -------------------------
00050 Rancor_Idle
00051 -------------------------
00052 */
00053 void Rancor_Idle( void )
00054 {
00055         NPCInfo->localState = LSTATE_CLEAR;
00056 
00057         //If we have somewhere to go, then do that
00058         if ( UpdateGoal() )
00059         {
00060                 ucmd.buttons &= ~BUTTON_WALKING;
00061                 NPC_MoveToGoal( qtrue );
00062         }
00063 }
00064 
00065 
00066 qboolean Rancor_CheckRoar( gentity_t *self )
00067 {
00068         if ( !self->wait )
00069         {//haven't ever gotten mad yet
00070                 self->wait = 1;//do this only once
00071                 self->client->ps.eFlags2 |= EF2_ALERTED;
00072                 NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00073                 TIMER_Set( self, "rageTime", self->client->ps.legsTimer );
00074                 return qtrue;
00075         }
00076         return qfalse;
00077 }
00078 /*
00079 -------------------------
00080 Rancor_Patrol
00081 -------------------------
00082 */
00083 void Rancor_Patrol( void )
00084 {
00085         NPCInfo->localState = LSTATE_CLEAR;
00086 
00087         //If we have somewhere to go, then do that
00088         if ( UpdateGoal() )
00089         {
00090                 ucmd.buttons &= ~BUTTON_WALKING;
00091                 NPC_MoveToGoal( qtrue );
00092         }
00093         else
00094         {
00095                 if ( TIMER_Done( NPC, "patrolTime" ))
00096                 {
00097                         TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 );
00098                 }
00099         }
00100 
00101         if ( NPC_CheckEnemyExt( qtrue ) == qfalse )
00102         {
00103                 Rancor_Idle();
00104                 return;
00105         }
00106         Rancor_CheckRoar( NPC );
00107         TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
00108 }
00109  
00110 /*
00111 -------------------------
00112 Rancor_Move
00113 -------------------------
00114 */
00115 void Rancor_Move( qboolean visible )
00116 {
00117         if ( NPCInfo->localState != LSTATE_WAITING )
00118         {
00119                 NPCInfo->goalEntity = NPC->enemy;
00120                 if ( !NPC_MoveToGoal( qtrue ) )
00121                 {
00122                         NPCInfo->consecutiveBlockedMoves++;
00123                 }
00124                 else
00125                 {
00126                         NPCInfo->consecutiveBlockedMoves = 0;
00127                 }
00128                 NPCInfo->goalRadius = MAX_DISTANCE;     // just get us within combat range
00129         }
00130 }
00131 
00132 //---------------------------------------------------------
00133 //extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
00134 extern void G_Knockdown( gentity_t *victim );
00135 extern void G_Dismember( gentity_t *ent, gentity_t *enemy, vec3_t point, int limbType, float limbRollBase, float limbPitchBase, int deathAnim, qboolean postDeath );
00136 //extern qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force );
00137 extern float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex );
00138 extern int NPC_GetEntsNearBolt( int *radiusEnts, float radius, int boltIndex, vec3_t boltOrg );
00139 
00140 void Rancor_DropVictim( gentity_t *self )
00141 {
00142         //FIXME: if Rancor dies, it should drop its victim.
00143         //FIXME: if Rancor is removed, it must remove its victim.
00144         if ( self->activator )
00145         {
00146                 if ( self->activator->client )
00147                 {
00148                         self->activator->client->ps.eFlags2 &= ~EF2_HELD_BY_MONSTER;
00149                         self->activator->client->ps.hasLookTarget = qfalse;
00150                         self->activator->client->ps.lookTarget = ENTITYNUM_NONE;
00151                         self->activator->client->ps.viewangles[ROLL] = 0;
00152                         SetClientViewAngle( self->activator, self->activator->client->ps.viewangles );
00153                         self->activator->r.currentAngles[PITCH] = self->activator->r.currentAngles[ROLL] = 0;
00154                         G_SetAngles( self->activator, self->activator->r.currentAngles );
00155                 }
00156                 if ( self->activator->health <= 0 )
00157                 {
00158                         //if ( self->activator->s.number )
00159                         {//never free player
00160                                 if ( self->count == 1 )
00161                                 {//in my hand, just drop them
00162                                         if ( self->activator->client )
00163                                         {
00164                                                 self->activator->client->ps.legsTimer = self->activator->client->ps.torsoTimer = 0;
00165                                                 //FIXME: ragdoll?
00166                                         }
00167                                 }
00168                                 else
00169                                 {
00170                                         if ( self->activator->client )
00171                                         {
00172                                                 self->activator->client->ps.eFlags |= EF_NODRAW;//so his corpse doesn't drop out of me...
00173                                         }
00174                                         //G_FreeEntity( self->activator );
00175                                 }
00176                         }
00177                 }
00178                 else
00179                 {
00180                         if ( self->activator->NPC )
00181                         {//start thinking again
00182                                 self->activator->NPC->nextBStateThink = level.time;
00183                         }
00184                         //clear their anim and let them fall
00185                         self->activator->client->ps.legsTimer = self->activator->client->ps.torsoTimer = 0;
00186                 }
00187                 if ( self->enemy == self->activator )
00188                 {
00189                         self->enemy = NULL;
00190                 }
00191                 self->activator = NULL;
00192         }
00193         self->count = 0;//drop him
00194 }
00195 
00196 void Rancor_Swing( qboolean tryGrab )
00197 {
00198         int                     radiusEntNums[128];
00199         int                     numEnts;
00200         const float     radius = 88;
00201         const float     radiusSquared = (radius*radius);
00202         int                     i;
00203         vec3_t          boltOrg;
00204 
00205         numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.handRBolt, boltOrg );
00206 
00207         for ( i = 0; i < numEnts; i++ )
00208         {
00209                 gentity_t *radiusEnt = &g_entities[radiusEntNums[i]];
00210                 if ( !radiusEnt->inuse )
00211                 {
00212                         continue;
00213                 }
00214                 
00215                 if ( radiusEnt == NPC )
00216                 {//Skip the rancor ent
00217                         continue;
00218                 }
00219                 
00220                 if ( radiusEnt->client == NULL )
00221                 {//must be a client
00222                         continue;
00223                 }
00224 
00225                 if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
00226                 {//can't be one already being held
00227                         continue;
00228                 }
00229                 
00230                 if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared )
00231                 {
00232                         if ( tryGrab 
00233                                 && NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth!
00234                                 && radiusEnt->client->NPC_class != CLASS_RANCOR
00235                                 && radiusEnt->client->NPC_class != CLASS_GALAKMECH
00236                                 && radiusEnt->client->NPC_class != CLASS_ATST
00237                                 && radiusEnt->client->NPC_class != CLASS_GONK
00238                                 && radiusEnt->client->NPC_class != CLASS_R2D2
00239                                 && radiusEnt->client->NPC_class != CLASS_R5D2
00240                                 && radiusEnt->client->NPC_class != CLASS_MARK1
00241                                 && radiusEnt->client->NPC_class != CLASS_MARK2
00242                                 && radiusEnt->client->NPC_class != CLASS_MOUSE
00243                                 && radiusEnt->client->NPC_class != CLASS_PROBE
00244                                 && radiusEnt->client->NPC_class != CLASS_SEEKER
00245                                 && radiusEnt->client->NPC_class != CLASS_REMOTE
00246                                 && radiusEnt->client->NPC_class != CLASS_SENTRY
00247                                 && radiusEnt->client->NPC_class != CLASS_INTERROGATOR
00248                                 && radiusEnt->client->NPC_class != CLASS_VEHICLE )
00249                         {//grab
00250                                 if ( NPC->count == 2 )
00251                                 {//have one in my mouth, remove him
00252                                         TIMER_Remove( NPC, "clearGrabbed" );
00253                                         Rancor_DropVictim( NPC );
00254                                 }
00255                                 NPC->enemy = radiusEnt;//make him my new best friend
00256                                 radiusEnt->client->ps.eFlags2 |= EF2_HELD_BY_MONSTER;
00257                                 //FIXME: this makes it so that the victim can't hit us with shots!  Just use activator or something
00258                                 radiusEnt->client->ps.hasLookTarget = qtrue;
00259                                 radiusEnt->client->ps.lookTarget = NPC->s.number;
00260                                 NPC->activator = radiusEnt;//remember him
00261                                 NPC->count = 1;//in my hand
00262                                 //wait to attack
00263                                 TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + Q_irand(500, 2500) );
00264                                 if ( radiusEnt->health > 0 && radiusEnt->pain )
00265                                 {//do pain on enemy
00266                                         radiusEnt->pain( radiusEnt, NPC, 100 );
00267                                         //GEntity_PainFunc( radiusEnt, NPC, NPC, radiusEnt->r.currentOrigin, 0, MOD_CRUSH );
00268                                 }
00269                                 else if ( radiusEnt->client )
00270                                 {
00271                                         radiusEnt->client->ps.forceHandExtend = HANDEXTEND_NONE;
00272                                         radiusEnt->client->ps.forceHandExtendTime = 0;
00273                                         NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00274                                 }
00275                         }
00276                         else
00277                         {//smack
00278                                 vec3_t pushDir;
00279                                 vec3_t angs;
00280 
00281                                 G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
00282                                 //actually push the enemy
00283                                 /*
00284                                 //VectorSubtract( radiusEnt->r.currentOrigin, boltOrg, pushDir );
00285                                 VectorSubtract( radiusEnt->r.currentOrigin, NPC->r.currentOrigin, pushDir );
00286                                 pushDir[2] = Q_flrand( 100, 200 );
00287                                 VectorNormalize( pushDir );
00288                                 */
00289                                 VectorCopy( NPC->client->ps.viewangles, angs );
00290                                 angs[YAW] += flrand( 25, 50 );
00291                                 angs[PITCH] = flrand( -25, -15 );
00292                                 AngleVectors( angs, pushDir, NULL, NULL );
00293                                 if ( radiusEnt->client->NPC_class != CLASS_RANCOR
00294                                         && radiusEnt->client->NPC_class != CLASS_ATST )
00295                                 {
00296                                         G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE );
00297                                         G_Throw( radiusEnt, pushDir, 250 );
00298                                         if ( radiusEnt->health > 0 )
00299                                         {//do pain on enemy
00300                                                 G_Knockdown( radiusEnt );//, NPC, pushDir, 100, qtrue );
00301                                         }
00302                                 }
00303                         }
00304                 }
00305         }
00306 }
00307 
00308 void Rancor_Smash( void )
00309 {
00310         int                     radiusEntNums[128];
00311         int                     numEnts;
00312         const float     radius = 128;
00313         const float     halfRadSquared = ((radius/2)*(radius/2));
00314         const float     radiusSquared = (radius*radius);
00315         float           distSq;
00316         int                     i;
00317         vec3_t          boltOrg;
00318 
00319         AddSoundEvent( NPC, NPC->r.currentOrigin, 512, AEL_DANGER, qfalse );//, qtrue );
00320 
00321         numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.handLBolt, boltOrg );
00322 
00323         for ( i = 0; i < numEnts; i++ )
00324         {
00325                 gentity_t *radiusEnt = &g_entities[radiusEntNums[i]];
00326                 if ( !radiusEnt->inuse )
00327                 {
00328                         continue;
00329                 }
00330                 
00331                 if ( radiusEnt == NPC )
00332                 {//Skip the rancor ent
00333                         continue;
00334                 }
00335                 
00336                 if ( radiusEnt->client == NULL )
00337                 {//must be a client
00338                         continue;
00339                 }
00340 
00341                 if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
00342                 {//can't be one being held
00343                         continue;
00344                 }
00345                 
00346                 distSq = DistanceSquared( radiusEnt->r.currentOrigin, boltOrg );
00347                 if ( distSq <= radiusSquared )
00348                 {
00349                         G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
00350                         if ( distSq < halfRadSquared )
00351                         {//close enough to do damage, too
00352                                 G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 10, 25 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE );
00353                         }
00354                         if ( radiusEnt->health > 0 
00355                                 && radiusEnt->client
00356                                 && radiusEnt->client->NPC_class != CLASS_RANCOR
00357                                 && radiusEnt->client->NPC_class != CLASS_ATST )
00358                         {
00359                                 if ( distSq < halfRadSquared 
00360                                         || radiusEnt->client->ps.groundEntityNum != ENTITYNUM_NONE )
00361                                 {//within range of my fist or withing ground-shaking range and not in the air
00362                                         G_Knockdown( radiusEnt );//, NPC, vec3_origin, 100, qtrue );
00363                                 }
00364                         }
00365                 }
00366         }
00367 }
00368 
00369 void Rancor_Bite( void )
00370 {
00371         int                     radiusEntNums[128];
00372         int                     numEnts;
00373         const float     radius = 100;
00374         const float     radiusSquared = (radius*radius);
00375         int                     i;
00376         vec3_t          boltOrg;
00377 
00378         numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.crotchBolt, boltOrg );//was gutBolt?
00379 
00380         for ( i = 0; i < numEnts; i++ )
00381         {
00382                 gentity_t *radiusEnt = &g_entities[radiusEntNums[i]];
00383                 if ( !radiusEnt->inuse )
00384                 {
00385                         continue;
00386                 }
00387                 
00388                 if ( radiusEnt == NPC )
00389                 {//Skip the rancor ent
00390                         continue;
00391                 }
00392                 
00393                 if ( radiusEnt->client == NULL )
00394                 {//must be a client
00395                         continue;
00396                 }
00397 
00398                 if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
00399                 {//can't be one already being held
00400                         continue;
00401                 }
00402                 
00403                 if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared )
00404                 {
00405                         G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 15, 30 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE );
00406                         if ( radiusEnt->health <= 0 && radiusEnt->client )
00407                         {//killed them, chance of dismembering
00408                                 if ( !Q_irand( 0, 1 ) )
00409                                 {//bite something off
00410                                         int hitLoc = Q_irand( G2_MODELPART_HEAD, G2_MODELPART_RLEG );
00411                                         if ( hitLoc == G2_MODELPART_HEAD )
00412                                         {
00413                                                 NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00414                                         }
00415                                         else if ( hitLoc == G2_MODELPART_WAIST )
00416                                         {
00417                                                 NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00418                                         }
00419                                         //radiusEnt->client->dismembered = qfalse;
00420                                         //FIXME: the limb should just disappear, cuz I ate it
00421                                         G_Dismember( radiusEnt, NPC, radiusEnt->r.currentOrigin, hitLoc, 90, 0, radiusEnt->client->ps.torsoAnim, qtrue);
00422                                         //G_DoDismemberment( radiusEnt, radiusEnt->r.currentOrigin, MOD_SABER, 1000, hitLoc, qtrue );
00423                                 }
00424                         }
00425                         G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
00426                 }
00427         }
00428 }
00429 //------------------------------
00430 extern void TossClientItems( gentity_t *self );
00431 void Rancor_Attack( float distance, qboolean doCharge )
00432 {
00433         if ( !TIMER_Exists( NPC, "attacking" ) )
00434         {
00435                 if ( NPC->count == 2 && NPC->activator )
00436                 {
00437                 }
00438                 else if ( NPC->count == 1 && NPC->activator )
00439                 {//holding enemy
00440                         if ( NPC->activator->health > 0 && Q_irand( 0, 1 ) )
00441                         {//quick bite
00442                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00443                                 TIMER_Set( NPC, "attack_dmg", 450 );
00444                         }
00445                         else
00446                         {//full eat
00447                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00448                                 TIMER_Set( NPC, "attack_dmg", 900 );
00449                                 //Make victim scream in fright
00450                                 if ( NPC->activator->health > 0 && NPC->activator->client )
00451                                 {
00452                                         G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 );
00453                                         NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00454                                         if ( NPC->activator->NPC )
00455                                         {//no more thinking for you
00456                                                 TossClientItems( NPC );
00457                                                 NPC->activator->NPC->nextBStateThink = Q3_INFINITE;
00458                                         }
00459                                 }
00460                         }
00461                 }
00462                 else if ( NPC->enemy->health > 0 && doCharge )
00463                 {//charge
00464                         vec3_t  fwd, yawAng;
00465                         VectorSet( yawAng, 0, NPC->client->ps.viewangles[YAW], 0 );
00466                         AngleVectors( yawAng, fwd, NULL, NULL );
00467                         VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity );
00468                         NPC->client->ps.velocity[2] = 150;
00469                         NPC->client->ps.groundEntityNum = ENTITYNUM_NONE;
00470 
00471                         NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00472                         TIMER_Set( NPC, "attack_dmg", 1250 );
00473                 }
00474                 else if ( !Q_irand(0, 1) )
00475                 {//smash
00476                         NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00477                         TIMER_Set( NPC, "attack_dmg", 1000 );
00478                 }
00479                 else
00480                 {//try to grab
00481                         NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00482                         TIMER_Set( NPC, "attack_dmg", 1000 );
00483                 }
00484 
00485                 TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + random() * 200 );
00486         }
00487 
00488         // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks
00489 
00490         if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) )
00491         {
00492                 vec3_t shakePos;
00493                 switch ( NPC->client->ps.legsAnim )
00494                 {
00495                 case BOTH_MELEE1:
00496                         Rancor_Smash();
00497                         G_GetBoltPosition( NPC, NPC->client->renderInfo.handLBolt, shakePos, 0 );
00498                         G_ScreenShake( shakePos, NULL, 4.0f, 1000, qfalse );
00499                         //CGCam_Shake( 1.0f*playerDist/128.0f, 1000 );
00500                         break;
00501                 case BOTH_MELEE2:
00502                         Rancor_Bite();
00503                         TIMER_Set( NPC, "attack_dmg2", 450 );
00504                         break;
00505                 case BOTH_ATTACK1:
00506                         if ( NPC->count == 1 && NPC->activator )
00507                         {
00508                                 G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE );
00509                                 if ( NPC->activator->health <= 0 )
00510                                 {//killed him
00511                                         //make it look like we bit his head off
00512                                         //NPC->activator->client->dismembered = qfalse;
00513                                         G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_HEAD, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue);
00514                                         //G_DoDismemberment( NPC->activator, NPC->activator->r.currentOrigin, MOD_SABER, 1000, HL_HEAD, qtrue );
00515                                         NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE;
00516                                         NPC->activator->client->ps.forceHandExtendTime = 0;
00517                                         NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00518                                 }
00519                                 G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
00520                         }
00521                         break;
00522                 case BOTH_ATTACK2:
00523                         //try to grab
00524                         Rancor_Swing( qtrue );
00525                         break;
00526                 case BOTH_ATTACK3:
00527                         if ( NPC->count == 1 && NPC->activator )
00528                         {
00529                                 //cut in half
00530                                 if ( NPC->activator->client )
00531                                 {
00532                                         //NPC->activator->client->dismembered = qfalse;
00533                                         G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue);
00534                                         //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue );
00535                                 }
00536                                 //KILL
00537                                 G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, NPC->enemy->health+10, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE );//
00538                                 if ( NPC->activator->client )
00539                                 {
00540                                         NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE;
00541                                         NPC->activator->client->ps.forceHandExtendTime = 0;
00542                                         NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00543                                 }
00544                                 TIMER_Set( NPC, "attack_dmg2", 1350 );
00545                                 G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
00546                                 G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health );
00547                         }
00548                         break;
00549                 }
00550         }
00551         else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) )
00552         {
00553                 switch ( NPC->client->ps.legsAnim )
00554                 {
00555                 case BOTH_MELEE1:
00556                         break;
00557                 case BOTH_MELEE2:
00558                         Rancor_Bite();
00559                         break;
00560                 case BOTH_ATTACK1:
00561                         break;
00562                 case BOTH_ATTACK2:
00563                         break;
00564                 case BOTH_ATTACK3:
00565                         if ( NPC->count == 1 && NPC->activator )
00566                         {//swallow victim
00567                                 G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
00568                                 //FIXME: sometimes end up with a live one in our mouths?
00569                                 //just make sure they're dead
00570                                 if ( NPC->activator->health > 0 )
00571                                 {
00572                                         //cut in half
00573                                         //NPC->activator->client->dismembered = qfalse;
00574                                         G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue);
00575                                         //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue );
00576                                         //KILL
00577                                         G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, NPC->enemy->health+10, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE );
00578                                         NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE;
00579                                         NPC->activator->client->ps.forceHandExtendTime = 0;
00580                                         NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00581                                         G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health );
00582                                 }
00583                                 if ( NPC->activator->client )
00584                                 {//*sigh*, can't get tags right, just remove them?
00585                                         NPC->activator->client->ps.eFlags |= EF_NODRAW;
00586                                 }
00587                                 NPC->count = 2;
00588                                 TIMER_Set( NPC, "clearGrabbed", 2600 );
00589                         }
00590                         break;
00591                 }
00592         }
00593         else if ( NPC->client->ps.legsAnim == BOTH_ATTACK2 )
00594         {
00595                 if ( NPC->client->ps.legsTimer >= 1200 && NPC->client->ps.legsTimer <= 1350 )
00596                 {
00597                         if ( Q_irand( 0, 2 ) )
00598                         {
00599                                 Rancor_Swing( qfalse );
00600                         }
00601                         else
00602                         {
00603                                 Rancor_Swing( qtrue );
00604                         }
00605                 }
00606                 else if ( NPC->client->ps.legsTimer >= 1100 && NPC->client->ps.legsTimer <= 1550 )
00607                 {
00608                         Rancor_Swing( qtrue );
00609                 }
00610         }
00611 
00612         // Just using this to remove the attacking flag at the right time
00613         TIMER_Done2( NPC, "attacking", qtrue );
00614 }
00615 
00616 //----------------------------------
00617 void Rancor_Combat( void )
00618 {
00619         if ( NPC->count )
00620         {//holding my enemy
00621                 if ( TIMER_Done2( NPC, "takingPain", qtrue ))
00622                 {
00623                         NPCInfo->localState = LSTATE_CLEAR;
00624                 }
00625                 else
00626                 {
00627                         Rancor_Attack( 0, qfalse );
00628                 }
00629                 NPC_UpdateAngles( qtrue, qtrue );
00630                 return;
00631         }
00632         // If we cannot see our target or we have somewhere to go, then do that
00633         if ( !NPC_ClearLOS4( NPC->enemy ) )//|| UpdateGoal( ))
00634         {
00635                 NPCInfo->combatMove = qtrue;
00636                 NPCInfo->goalEntity = NPC->enemy;
00637                 NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE;      // just get us within combat range
00638 
00639                 if ( !NPC_MoveToGoal( qtrue ) )
00640                 {//couldn't go after him?  Look for a new one
00641                         TIMER_Set( NPC, "lookForNewEnemy", 0 );
00642                         NPCInfo->consecutiveBlockedMoves++;
00643                 }
00644                 else 
00645                 {
00646                         NPCInfo->consecutiveBlockedMoves = 0;
00647                 }
00648                 return;
00649         }
00650 
00651         // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb
00652         NPC_FaceEnemy( qtrue );
00653 
00654         {
00655                 float   distance;
00656                 qboolean        advance;
00657                 qboolean        doCharge;
00658 
00659                 distance        = Distance( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );        
00660                 advance = (qboolean)( distance > (NPC->r.maxs[0]+MIN_DISTANCE) ? qtrue : qfalse  );
00661                 doCharge = qfalse;
00662 
00663                 if ( advance )
00664                 {//have to get closer
00665                         vec3_t  yawOnlyAngles;
00666                         VectorSet( yawOnlyAngles, 0, NPC->r.currentAngles[YAW], 0 );
00667                         if ( NPC->enemy->health > 0
00668                                 && fabs(distance-250) <= 80 
00669                                 && InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, yawOnlyAngles, 30, 30 ) )
00670                         {
00671                                 if ( !Q_irand( 0, 9 ) )
00672                                 {//go for the charge
00673                                         doCharge = qtrue;
00674                                         advance = qfalse;
00675                                 }
00676                         }
00677                 }
00678 
00679                 if (( advance /*|| NPCInfo->localState == LSTATE_WAITING*/ ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack
00680                 {
00681                         if ( TIMER_Done2( NPC, "takingPain", qtrue ))
00682                         {
00683                                 NPCInfo->localState = LSTATE_CLEAR;
00684                         }
00685                         else
00686                         {
00687                                 Rancor_Move( 1 );
00688                         }
00689                 }
00690                 else
00691                 {
00692                         Rancor_Attack( distance, doCharge );
00693                 }
00694         }
00695 }
00696 
00697 /*
00698 -------------------------
00699 NPC_Rancor_Pain
00700 -------------------------
00701 */
00702 //void NPC_Rancor_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) 
00703 void NPC_Rancor_Pain( gentity_t *self, gentity_t *attacker, int damage ) 
00704 {
00705         qboolean hitByRancor = qfalse;
00706         if ( attacker&&attacker->client&&attacker->client->NPC_class==CLASS_RANCOR )
00707         {
00708                 hitByRancor = qtrue;
00709         }
00710         if ( attacker 
00711                 && attacker->inuse 
00712                 && attacker != self->enemy
00713                 && !(attacker->flags&FL_NOTARGET) )
00714         {
00715                 if ( !self->count )
00716                 {
00717                         if ( (!attacker->s.number&&!Q_irand(0,3))
00718                                 || !self->enemy
00719                                 || self->enemy->health == 0
00720                                 || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR)
00721                                 || (self->NPC && self->NPC->consecutiveBlockedMoves>=10 && DistanceSquared( attacker->r.currentOrigin, self->r.currentOrigin ) < DistanceSquared( self->enemy->r.currentOrigin, self->r.currentOrigin )) ) 
00722                         {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker
00723                                 //FIXME: if can't nav to my enemy, take this guy if I can nav to him
00724                                 G_SetEnemy( self, attacker );
00725                                 TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
00726                                 if ( hitByRancor )
00727                                 {//stay mad at this Rancor for 2-5 secs before looking for attacker enemies
00728                                         TIMER_Set( self, "rancorInfight", Q_irand( 2000, 5000 ) );
00729                                 }
00730 
00731                         }
00732                 }
00733         }
00734         if ( (hitByRancor|| (self->count==1&&self->activator&&!Q_irand(0,4)) || Q_irand( 0, 200 ) < damage )//hit by rancor, hit while holding live victim, or took a lot of damage
00735                 && self->client->ps.legsAnim != BOTH_STAND1TO2
00736                 && TIMER_Done( self, "takingPain" ) )
00737         {
00738                 if ( !Rancor_CheckRoar( self ) )
00739                 {
00740                         if ( self->client->ps.legsAnim != BOTH_MELEE1
00741                                 && self->client->ps.legsAnim != BOTH_MELEE2
00742                                 && self->client->ps.legsAnim != BOTH_ATTACK2 )
00743                         {//cant interrupt one of the big attack anims
00744                                 /*
00745                                 if ( self->count != 1 
00746                                         || attacker == self->activator
00747                                         || (self->client->ps.legsAnim != BOTH_ATTACK1&&self->client->ps.legsAnim != BOTH_ATTACK3) )
00748                                 */
00749                                 {//if going to bite our victim, only victim can interrupt that anim
00750                                         if ( self->health > 100 || hitByRancor )
00751                                         {
00752                                                 TIMER_Remove( self, "attacking" );
00753 
00754                                                 VectorCopy( self->NPC->lastPathAngles, self->s.angles );
00755 
00756                                                 if ( self->count == 1 )
00757                                                 {
00758                                                         NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00759                                                 }
00760                                                 else
00761                                                 {
00762                                                         NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
00763                                                 }
00764                                                 TIMER_Set( self, "takingPain", self->client->ps.legsTimer+Q_irand(0, 500) );
00765 
00766                                                 if ( self->NPC )
00767                                                 {
00768                                                         self->NPC->localState = LSTATE_WAITING;
00769                                                 }
00770                                         }
00771                                 }
00772                         }
00773                 }
00774                 //let go
00775                 /*
00776                 if ( !Q_irand( 0, 3 ) && self->count == 1 )
00777                 {
00778                         Rancor_DropVictim( self );
00779                 }
00780                 */
00781         }
00782 }
00783 
00784 void Rancor_CheckDropVictim( void )
00785 {
00786         vec3_t mins;
00787         vec3_t maxs;
00788         vec3_t start; 
00789         vec3_t end; 
00790         trace_t trace;
00791 
00792         VectorSet( mins, NPC->activator->r.mins[0]-1, NPC->activator->r.mins[1]-1, 0 );
00793         VectorSet( maxs, NPC->activator->r.maxs[0]+1, NPC->activator->r.maxs[1]+1, 1 );
00794         VectorSet( start, NPC->activator->r.currentOrigin[0], NPC->activator->r.currentOrigin[1], NPC->activator->r.absmin[2] ); 
00795         VectorSet( end, NPC->activator->r.currentOrigin[0], NPC->activator->r.currentOrigin[1], NPC->activator->r.absmax[2]-1 ); 
00796 
00797         trap_Trace( &trace, start, mins, maxs, end, NPC->activator->s.number, NPC->activator->clipmask );
00798         if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0f )
00799         {
00800                 Rancor_DropVictim( NPC );
00801         }
00802 }
00803 
00804 //if he's stepping on things then crush them -rww
00805 void Rancor_Crush(void)
00806 {
00807         gentity_t *crush;
00808 
00809         if (!NPC ||
00810                 !NPC->client ||
00811                 NPC->client->ps.groundEntityNum >= ENTITYNUM_WORLD)
00812         { //nothing to crush
00813                 return;
00814         }
00815 
00816         crush = &g_entities[NPC->client->ps.groundEntityNum];
00817         if (crush->inuse && crush->client && !crush->localAnimIndex)
00818         { //a humanoid, smash them good.
00819                 G_Damage(crush, NPC, NPC, NULL, NPC->r.currentOrigin, 200, 0, MOD_CRUSH);
00820         }
00821 }
00822 
00823 /*
00824 -------------------------
00825 NPC_BSRancor_Default
00826 -------------------------
00827 */
00828 void NPC_BSRancor_Default( void )
00829 {
00830         AddSightEvent( NPC, NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, 50 );
00831 
00832         Rancor_Crush();
00833 
00834         NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM|EF2_GENERIC_NPC_FLAG);
00835         if ( NPC->count )
00836         {//holding someone
00837                 NPC->client->ps.eFlags2 |= EF2_USE_ALT_ANIM;
00838                 if ( NPC->count == 2 )
00839                 {//in my mouth
00840                         NPC->client->ps.eFlags2 |= EF2_GENERIC_NPC_FLAG;
00841                 }
00842         }
00843         else
00844         {
00845                 NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM|EF2_GENERIC_NPC_FLAG);
00846         }
00847 
00848         if ( TIMER_Done2( NPC, "clearGrabbed", qtrue ) )
00849         {
00850                 Rancor_DropVictim( NPC );
00851         }
00852         else if ( NPC->client->ps.legsAnim == BOTH_PAIN2 
00853                 && NPC->count == 1 
00854                 && NPC->activator )
00855         {
00856                 if ( !Q_irand( 0, 3 ) )
00857                 {
00858                         Rancor_CheckDropVictim();
00859                 }
00860         }
00861         if ( !TIMER_Done( NPC, "rageTime" ) )
00862         {//do nothing but roar first time we see an enemy
00863                 AddSoundEvent( NPC, NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, qfalse );//, qfalse );
00864                 NPC_FaceEnemy( qtrue );
00865                 return;
00866         }
00867         if ( NPC->enemy )
00868         {
00869                 /*
00870                 if ( NPC->enemy->client //enemy is a client
00871                         && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught
00872                         && NPC->enemy->enemy != NPC//enemy's enemy is not me
00873                         && (!NPC->enemy->enemy || !NPC->enemy->enemy->client || NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR) )//enemy's enemy is not a client or is not a rancor (which is as scary as me anyway)
00874                 {//they should be scared of ME and no-one else
00875                         G_SetEnemy( NPC->enemy, NPC );
00876                 }
00877                 */
00878                 if ( TIMER_Done(NPC,"angrynoise") )
00879                 {
00880                         G_Sound( NPC, CHAN_AUTO, G_SoundIndex( va("sound/chars/rancor/misc/anger%d.wav", Q_irand(1, 3))) );
00881 
00882                         TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) );
00883                 }
00884                 else
00885                 {
00886                         AddSoundEvent( NPC, NPC->r.currentOrigin, 512, AEL_DANGER_GREAT, qfalse );//, qfalse );
00887                 }
00888                 if ( NPC->count == 2 && NPC->client->ps.legsAnim == BOTH_ATTACK3 )
00889                 {//we're still chewing our enemy up
00890                         NPC_UpdateAngles( qtrue, qtrue );
00891                         return;
00892                 }
00893                 //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while
00894                 if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_RANCOR )
00895                 {//got mad at another Rancor, look for a valid enemy
00896                         if ( TIMER_Done( NPC, "rancorInfight" ) )
00897                         {
00898                                 NPC_CheckEnemyExt( qtrue );
00899                         }
00900                 }
00901                 else if ( !NPC->count )
00902                 {
00903                         if ( ValidEnemy( NPC->enemy ) == qfalse )
00904                         {
00905                                 TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now
00906                                 if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) )
00907                                 {//it's been a while since the enemy died, or enemy is completely gone, get bored with him
00908                                         NPC->enemy = NULL;
00909                                         Rancor_Patrol();
00910                                         NPC_UpdateAngles( qtrue, qtrue );
00911                                         return;
00912                                 }
00913                         }
00914                         if ( TIMER_Done( NPC, "lookForNewEnemy" ) )
00915                         {
00916                                 gentity_t *newEnemy, *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy?
00917                                 NPC->enemy = NULL;
00918                                 newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse );
00919                                 NPC->enemy = sav_enemy;
00920                                 if ( newEnemy && newEnemy != sav_enemy )
00921                                 {//picked up a new enemy!
00922                                         NPC->lastEnemy = NPC->enemy;
00923                                         G_SetEnemy( NPC, newEnemy );
00924                                         //hold this one for at least 5-15 seconds
00925                                         TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
00926                                 }
00927                                 else
00928                                 {//look again in 2-5 secs
00929                                         TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) );
00930                                 }
00931                         }
00932                 }
00933                 Rancor_Combat();
00934         }
00935         else 
00936         {
00937                 if ( TIMER_Done(NPC,"idlenoise") )
00938                 {
00939                         G_Sound( NPC, CHAN_AUTO, G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", Q_irand(1, 2))) );
00940 
00941                         TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) );
00942                         AddSoundEvent( NPC, NPC->r.currentOrigin, 384, AEL_DANGER, qfalse );//, qfalse );
00943                 }
00944                 if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
00945                 {
00946                         Rancor_Patrol();
00947                 }
00948                 else
00949                 {
00950                         Rancor_Idle();
00951                 }
00952         }
00953 
00954         NPC_UpdateAngles( qtrue, qtrue );
00955 }