codemp/game/NPC_AI_GalakMech.c

Go to the documentation of this file.
00001 #include "b_local.h"
00002 #include "g_nav.h"
00003 #include "anims.h"
00004 #include "w_saber.h"
00005 
00006 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00007 extern void NPC_AimAdjust( int change );
00008 extern qboolean WP_LobFire( gentity_t *self, vec3_t start, vec3_t target, vec3_t mins, vec3_t maxs, int clipmask, 
00009                                 vec3_t velocity, qboolean tracePath, int ignoreEntNum, int enemyNum,
00010                                 float minSpeed, float maxSpeed, float idealSpeed, qboolean mustHit );
00011 extern void G_SoundOnEnt (gentity_t *ent, soundChannel_t channel, const char *soundPath);
00012 
00013 #include "../namespace_begin.h"
00014 extern qboolean BG_CrouchAnim( int anim );
00015 #include "../namespace_end.h"
00016 
00017 //extern void NPC_Mark1_Part_Explode(gentity_t *self,int bolt);
00018 
00019 #define MELEE_DIST_SQUARED 6400//80*80
00020 #define MIN_LOB_DIST_SQUARED 65536//256*256
00021 #define MAX_LOB_DIST_SQUARED 200704//448*448
00022 #define REPEATER_ALT_SIZE                               3       // half of bbox size
00023 #define GENERATOR_HEALTH        25
00024 #define TURN_ON                         0x00000000
00025 #define TURN_OFF                        0x00000100
00026 #define GALAK_SHIELD_HEALTH     500
00027 
00028 static vec3_t shieldMins = {-60, -60, -24 };
00029 static vec3_t shieldMaxs = {60, 60, 80};
00030 
00031 extern qboolean NPC_CheckPlayerTeamStealth( void );
00032 
00033 static qboolean enemyLOS4;
00034 static qboolean enemyCS4;
00035 static qboolean hitAlly4;
00036 static qboolean faceEnemy4;
00037 static qboolean move4;
00038 static qboolean shoot4;
00039 static float    enemyDist4;
00040 static vec3_t   impactPos4;
00041 
00042 void NPC_GalakMech_Precache( void )
00043 {
00044         G_SoundIndex( "sound/weapons/galak/skewerhit.wav" );
00045         G_SoundIndex( "sound/weapons/galak/lasercharge.wav" );
00046         G_SoundIndex( "sound/weapons/galak/lasercutting.wav" );
00047         G_SoundIndex( "sound/weapons/galak/laserdamage.wav" );
00048 
00049         G_EffectIndex( "galak/trace_beam" );
00050         G_EffectIndex( "galak/beam_warmup" );
00051 //      G_EffectIndex( "small_chunks");
00052         G_EffectIndex( "env/med_explode2");
00053         G_EffectIndex( "env/small_explode2");
00054         G_EffectIndex( "galak/explode");
00055         G_EffectIndex( "blaster/smoke_bolton");
00056 //      G_EffectIndex( "env/exp_trail_comp");
00057 }
00058 
00059 void NPC_GalakMech_Init( gentity_t *ent )
00060 {
00061         if (ent->NPC->behaviorState != BS_CINEMATIC)
00062         {
00063                 ent->client->ps.stats[STAT_ARMOR] = GALAK_SHIELD_HEALTH;
00064                 ent->NPC->investigateCount = ent->NPC->investigateDebounceTime = 0;
00065                 ent->flags |= FL_SHIELDED;//reflect normal shots
00066                 //rwwFIXMEFIXME: Support PW_GALAK_SHIELD
00067                 //ent->client->ps.powerups[PW_GALAK_SHIELD] = Q3_INFINITE;//temp, for effect
00068                 //ent->fx_time = level.time;
00069                 VectorSet( ent->r.mins, -60, -60, -24 );
00070                 VectorSet( ent->r.maxs, 60, 60, 80 );
00071                 ent->flags |= FL_NO_KNOCKBACK;//don't get pushed
00072                 TIMER_Set( ent, "attackDelay", 0 );     //FIXME: Slant for difficulty levels
00073                 TIMER_Set( ent, "flee", 0 );
00074                 TIMER_Set( ent, "smackTime", 0 );
00075                 TIMER_Set( ent, "beamDelay", 0 );
00076                 TIMER_Set( ent, "noLob", 0 );
00077                 TIMER_Set( ent, "noRapid", 0 );
00078                 TIMER_Set( ent, "talkDebounce", 0 );
00079 
00080                 NPC_SetSurfaceOnOff( ent, "torso_shield", TURN_ON );
00081                 NPC_SetSurfaceOnOff( ent, "torso_galakface", TURN_OFF );
00082                 NPC_SetSurfaceOnOff( ent, "torso_galakhead", TURN_OFF );
00083                 NPC_SetSurfaceOnOff( ent, "torso_eyes_mouth", TURN_OFF );
00084                 NPC_SetSurfaceOnOff( ent, "torso_collar", TURN_OFF );
00085                 NPC_SetSurfaceOnOff( ent, "torso_galaktorso", TURN_OFF );
00086         }
00087         else
00088         {
00089 //              NPC_SetSurfaceOnOff( ent, "helmet", TURN_OFF );
00090                 NPC_SetSurfaceOnOff( ent, "torso_shield", TURN_OFF );
00091                 NPC_SetSurfaceOnOff( ent, "torso_galakface", TURN_ON );
00092                 NPC_SetSurfaceOnOff( ent, "torso_galakhead", TURN_ON );
00093                 NPC_SetSurfaceOnOff( ent, "torso_eyes_mouth", TURN_ON );
00094                 NPC_SetSurfaceOnOff( ent, "torso_collar", TURN_ON );
00095                 NPC_SetSurfaceOnOff( ent, "torso_galaktorso", TURN_ON );
00096         }
00097 
00098 }
00099 
00100 //-----------------------------------------------------------------
00101 static void GM_CreateExplosion( gentity_t *self, const int boltID, qboolean doSmall ) //doSmall = qfalse
00102 {
00103         if ( boltID >=0 )
00104         {
00105                 mdxaBone_t      boltMatrix;
00106                 vec3_t          org, dir;
00107 
00108                 trap_G2API_GetBoltMatrix( self->ghoul2, 0, 
00109                                         boltID,
00110                                         &boltMatrix, self->r.currentAngles, self->r.currentOrigin, level.time,
00111                                         NULL, self->modelScale );
00112 
00113                 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org );
00114                 BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir );
00115 
00116                 if ( doSmall )
00117                 {
00118                         G_PlayEffectID( G_EffectIndex("env/small_explode2"), org, dir );
00119                 }
00120                 else
00121                 {
00122                         G_PlayEffectID( G_EffectIndex("env/med_explode2"), org, dir );
00123                 }
00124         }
00125 }
00126 
00127 /*
00128 -------------------------
00129 GM_Dying
00130 -------------------------
00131 */
00132 
00133 void GM_Dying( gentity_t *self )
00134 {
00135         if ( level.time - self->s.time < 4000 )
00136         {//FIXME: need a real effect
00137                 //self->s.powerups |= ( 1 << PW_SHOCKED );
00138                 //self->client->ps.powerups[PW_SHOCKED] = level.time + 1000;
00139                 self->client->ps.electrifyTime = level.time + 1000;
00140                 if ( TIMER_Done( self, "dyingExplosion" ) )
00141                 {
00142                         int     newBolt;
00143                         switch ( Q_irand( 1, 14 ) )
00144                         {
00145                         // Find place to generate explosion
00146                         case 1:
00147                                 if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "r_hand" ))
00148                                 {//r_hand still there
00149                                         GM_CreateExplosion( self, trap_G2API_AddBolt(self->ghoul2, 0, "*flasha"), qtrue );
00150                                         NPC_SetSurfaceOnOff( self, "r_hand", TURN_OFF );
00151                                 }
00152                                 else if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "r_arm_middle" ))
00153                                 {//r_arm_middle still there
00154                                         newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*r_arm_elbow" );
00155                                         NPC_SetSurfaceOnOff( self, "r_arm_middle", TURN_OFF );
00156                                 }
00157                                 break;
00158                         case 2:
00159                                 //FIXME: do only once?
00160                                 if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_hand" ))
00161                                 {//l_hand still there
00162                                         GM_CreateExplosion( self, trap_G2API_AddBolt(self->ghoul2, 0, "*flashc"), qfalse );
00163                                         NPC_SetSurfaceOnOff( self, "l_hand", TURN_OFF );
00164                                 }
00165                                 else if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm_wrist" ))
00166                                 {//l_arm_wrist still there
00167                                         newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_arm_cap_l_hand" );
00168                                         NPC_SetSurfaceOnOff( self, "l_arm_wrist", TURN_OFF );
00169                                 }
00170                                 else if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm_middle" ))
00171                                 {//l_arm_middle still there
00172                                         newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_arm_cap_l_hand" );
00173                                         NPC_SetSurfaceOnOff( self, "l_arm_middle", TURN_OFF );
00174                                 }
00175                                 else if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm_augment" ))
00176                                 {//l_arm_augment still there
00177                                         newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_arm_elbow" );
00178                                         NPC_SetSurfaceOnOff( self, "l_arm_augment", TURN_OFF );
00179                                 }
00180                                 break;
00181                         case 3:
00182                         case 4:
00183                                 newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*hip_fr" );
00184                                 GM_CreateExplosion( self, newBolt, qfalse );
00185                                 break;
00186                         case 5:
00187                         case 6:
00188                                 newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*shldr_l" );
00189                                 GM_CreateExplosion( self, newBolt, qfalse );
00190                                 break;
00191                         case 7:
00192                         case 8:
00193                                 newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*uchest_r" );
00194                                 GM_CreateExplosion( self, newBolt, qfalse );
00195                                 break;
00196                         case 9:
00197                         case 10:
00198                                 GM_CreateExplosion( self, self->client->renderInfo.headBolt, qfalse );
00199                                 break;
00200                         case 11:
00201                                 newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_leg_knee" );
00202                                 GM_CreateExplosion( self, newBolt, qtrue );
00203                                 break;
00204                         case 12:
00205                                 newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*r_leg_knee" );
00206                                 GM_CreateExplosion( self, newBolt, qtrue );
00207                                 break;
00208                         case 13:
00209                                 newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_leg_foot" );
00210                                 GM_CreateExplosion( self, newBolt, qtrue );
00211                                 break;
00212                         case 14:
00213                                 newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*r_leg_foot" );
00214                                 GM_CreateExplosion( self, newBolt, qtrue );
00215                                 break;
00216                         }
00217 
00218                         TIMER_Set( self, "dyingExplosion", Q_irand( 300, 1100 ) );
00219                 }
00220         }
00221         else
00222         {//one final, huge explosion
00223                 G_PlayEffectID( G_EffectIndex("galak/explode"), self->r.currentOrigin, vec3_origin );
00224 //              G_PlayEffect( "small_chunks", self->r.currentOrigin );
00225 //              G_PlayEffect( "env/exp_trail_comp", self->r.currentOrigin, self->currentAngles );
00226                 self->nextthink = level.time + FRAMETIME;
00227                 self->think = G_FreeEntity;
00228         }
00229 }
00230 
00231 /*
00232 -------------------------
00233 NPC_GM_Pain
00234 -------------------------
00235 */
00236 
00237 extern void NPC_SetPainEvent( gentity_t *self );
00238 void NPC_GM_Pain(gentity_t *self, gentity_t *attacker, int damage)
00239 {
00240         vec3_t point;
00241         gentity_t *inflictor = attacker;
00242         int hitLoc = 1;
00243         int mod = gPainMOD;
00244 
00245         VectorCopy(gPainPoint, point);
00246 
00247         //if ( self->client->ps.powerups[PW_GALAK_SHIELD] == 0 )
00248         if (0) //rwwFIXMEFIXME: do all of this
00249         {//shield is currently down
00250                 //FIXME: allow for radius damage?
00251                 /*
00252                 if ( (hitLoc==HL_GENERIC1) && (self->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH) )
00253                 {
00254                         int newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*antenna_base" );
00255                         if ( newBolt != -1 )
00256                         {
00257                                 GM_CreateExplosion( self, newBolt, qfalse );
00258                         }
00259 
00260                         NPC_SetSurfaceOnOff( self, "torso_shield", TURN_OFF );
00261                         NPC_SetSurfaceOnOff( self, "torso_antenna", TURN_OFF );
00262                         NPC_SetSurfaceOnOff( self, "torso_antenna_base_cap", TURN_ON );
00263                         self->client->ps.powerups[PW_GALAK_SHIELD] = 0;//temp, for effect
00264                         self->client->ps.stats[STAT_ARMOR] = 0;//no more armor
00265                         self->NPC->investigateDebounceTime = 0;//stop recharging
00266 
00267                         NPC_SetAnim( self, SETANIM_BOTH, BOTH_ALERT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00268                         TIMER_Set( self, "attackDelay", self->client->ps.torsoTimer );
00269                         G_AddEvent( self, Q_irand( EV_DEATH1, EV_DEATH3 ), self->health );
00270                 }
00271                 */
00272         }
00273         else
00274         {//store the point for shield impact
00275                 if ( point )
00276                 {
00277                 //      VectorCopy( point, self->pos4 );
00278                 //      self->client->poisonTime = level.time;
00279                         //rwwFIXMEFIXME: ..do this is as well.
00280                 }
00281         }
00282 
00283         if ( !self->lockCount && self->client->ps.torsoTimer <= 0 )
00284         {//don't interrupt laser sweep attack or other special attacks/moves
00285                 if ( self->count < 4 && self->health > 100 && hitLoc != HL_GENERIC1 )
00286                 {
00287                         if ( self->delay < level.time )
00288                         {
00289                                 int speech;
00290                                 switch( self->count )
00291                                 {
00292                                 default:
00293                                 case 0:
00294                                         speech = EV_PUSHED1;
00295                                         break;
00296                                 case 1:
00297                                         speech = EV_PUSHED2;
00298                                         break;
00299                                 case 2:
00300                                         speech = EV_PUSHED3;
00301                                         break;
00302                                 case 3:
00303                                         speech = EV_DETECTED1;
00304                                         break;
00305                                 }
00306                                 self->count++;
00307                                 self->NPC->blockedSpeechDebounceTime = 0;
00308                                 G_AddVoiceEvent( self, speech, Q_irand( 3000, 5000 ) );
00309                                 self->delay = level.time + Q_irand( 5000, 7000 );
00310                         }
00311                 }
00312                 else
00313                 {
00314                         NPC_Pain(self, attacker, damage);
00315                 }
00316         }
00317         else if ( hitLoc == HL_GENERIC1 )
00318         {
00319                 NPC_SetPainEvent( self );
00320                 //self->s.powerups |= ( 1 << PW_SHOCKED );
00321                 //self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 );
00322                 self->client->ps.electrifyTime = level.time + Q_irand(500, 2500);
00323         }
00324 
00325         if ( inflictor && inflictor->lastEnemy == self )
00326         {//He force-pushed my own lobfires back at me
00327                 if ( mod == MOD_REPEATER_ALT && !Q_irand( 0, 2 ) )
00328                 {
00329                         if ( TIMER_Done( self, "noRapid" ) )
00330                         {
00331                                 self->NPC->scriptFlags &= ~SCF_ALT_FIRE;
00332                                 self->alt_fire = qfalse;
00333                                 TIMER_Set( self, "noLob", Q_irand( 2000, 6000 ) );
00334                         }
00335                         else
00336                         {//hopefully this will make us fire the laser
00337                                 TIMER_Set( self, "noLob", Q_irand( 1000, 2000 ) );
00338                         }
00339                 }
00340                 else if ( mod == MOD_REPEATER && !Q_irand( 0, 5 ) )
00341                 {
00342                         if ( TIMER_Done( self, "noLob" ) )
00343                         {
00344                                 self->NPC->scriptFlags |= SCF_ALT_FIRE;
00345                                 self->alt_fire = qtrue;
00346                                 TIMER_Set( self, "noRapid", Q_irand( 2000, 6000 ) );
00347                         }
00348                         else
00349                         {//hopefully this will make us fire the laser
00350                                 TIMER_Set( self, "noRapid", Q_irand( 1000, 2000 ) );
00351                         }
00352                 }
00353         }
00354 }
00355 
00356 /*
00357 -------------------------
00358 GM_HoldPosition
00359 -------------------------
00360 */
00361 
00362 static void GM_HoldPosition( void )
00363 {
00364         NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
00365         if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
00366         {//don't have a script waiting for me to get to my point, okay to stop trying and stand
00367                 NPCInfo->goalEntity = NULL;
00368         }
00369 }
00370 
00371 /*
00372 -------------------------
00373 GM_Move
00374 -------------------------
00375 */
00376 static qboolean GM_Move( void )
00377 {
00378         qboolean moved;
00379         navInfo_t info;
00380 
00381         NPCInfo->combatMove = qtrue;//always move straight toward our goal
00382 
00383         moved = NPC_MoveToGoal( qtrue );
00384         
00385         //Get the move info
00386         NAV_GetLastMove( &info );
00387 
00388         //FIXME: if we bump into another one of our guys and can't get around him, just stop!
00389         //If we hit our target, then stop and fire!
00390         if ( info.flags & NIF_COLLISION ) 
00391         {
00392                 if ( info.blocker == NPC->enemy )
00393                 {
00394                         GM_HoldPosition();
00395                 }
00396         }
00397 
00398         //If our move failed, then reset
00399         if ( moved == qfalse )
00400         {//FIXME: if we're going to a combat point, need to pick a different one
00401                 if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
00402                 {//can't transfer movegoal or stop when a script we're running is waiting to complete
00403                         GM_HoldPosition();
00404                 }
00405         }
00406 
00407         return moved;
00408 }
00409 
00410 /*
00411 -------------------------
00412 NPC_BSGM_Patrol
00413 -------------------------
00414 */
00415 
00416 void NPC_BSGM_Patrol( void )
00417 {
00418         if ( NPC_CheckPlayerTeamStealth() )
00419         {
00420                 NPC_UpdateAngles( qtrue, qtrue );
00421                 return;
00422         }
00423 
00424         //If we have somewhere to go, then do that
00425         if ( UpdateGoal() )
00426         {
00427                 ucmd.buttons |= BUTTON_WALKING;
00428                 NPC_MoveToGoal( qtrue );
00429         }
00430 
00431         NPC_UpdateAngles( qtrue, qtrue );
00432 }
00433 
00434 /*
00435 -------------------------
00436 GM_CheckMoveState
00437 -------------------------
00438 */
00439 
00440 static void GM_CheckMoveState( void )
00441 {
00442         if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
00443         {//moving toward a goal that a script is waiting on, so don't stop for anything!
00444                 move4 = qtrue;
00445         }
00446 
00447         //See if we're moving towards a goal, not the enemy
00448         if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
00449         {
00450                 //Did we make it?
00451                 if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, qfalse ) || 
00452                         ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) && enemyLOS4 && enemyDist4 <= 10000 ) )
00453                 {//either hit our navgoal or our navgoal was not a crucial (scripted) one (maybe a combat point) and we're scouting and found our enemy
00454                         NPC_ReachedGoal();
00455                         //don't attack right away
00456                         TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );   //FIXME: Slant for difficulty levels
00457                         return;
00458                 }
00459         }
00460 }
00461 
00462 /*
00463 -------------------------
00464 GM_CheckFireState
00465 -------------------------
00466 */
00467 
00468 static void GM_CheckFireState( void )
00469 {
00470         if ( enemyCS4 )
00471         {//if have a clear shot, always try
00472                 return;
00473         }
00474 
00475         if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
00476         {//if moving at all, don't do this
00477                 return;
00478         }
00479 
00480         //See if we should continue to fire on their last position
00481         if ( !hitAlly4 && NPCInfo->enemyLastSeenTime > 0 )
00482         {
00483                 if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )
00484                 {
00485                         if ( !Q_irand( 0, 10 ) )
00486                         {
00487                                 //Fire on the last known position
00488                                 vec3_t  muzzle, dir, angles;
00489                                 qboolean tooClose = qfalse;
00490                                 qboolean tooFar = qfalse;
00491                                 float distThreshold;
00492                                 float dist;
00493 
00494                                 CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
00495                                 if ( VectorCompare( impactPos4, vec3_origin ) )
00496                                 {//never checked ShotEntity this frame, so must do a trace...
00497                                         trace_t tr;
00498                                         //vec3_t        mins = {-2,-2,-2}, maxs = {2,2,2};
00499                                         vec3_t  forward, end;
00500                                         AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL );
00501                                         VectorMA( muzzle, 8192, forward, end );
00502                                         trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
00503                                         VectorCopy( tr.endpos, impactPos4 );
00504                                 }
00505 
00506                                 //see if impact would be too close to me
00507                                 distThreshold = 16384/*128*128*/;//default
00508                                 if ( NPC->s.weapon == WP_REPEATER )
00509                                 {
00510                                         if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
00511                                         {
00512                                                 distThreshold = 65536/*256*256*/;
00513                                         }
00514                                 }
00515 
00516                                 dist = DistanceSquared( impactPos4, muzzle );
00517 
00518                                 if ( dist < distThreshold )
00519                                 {//impact would be too close to me
00520                                         tooClose = qtrue;
00521                                 }
00522                                 else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 )
00523                                 {//we've haven't seen them in the last 5 seconds
00524                                         //see if it's too far from where he is
00525                                         distThreshold = 65536/*256*256*/;//default
00526                                         if ( NPC->s.weapon == WP_REPEATER )
00527                                         {
00528                                                 if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
00529                                                 {
00530                                                         distThreshold = 262144/*512*512*/;
00531                                                 }
00532                                         }
00533                                         dist = DistanceSquared( impactPos4, NPCInfo->enemyLastSeenLocation );
00534                                         if ( dist > distThreshold )
00535                                         {//impact would be too far from enemy
00536                                                 tooFar = qtrue;
00537                                         }
00538                                 }
00539 
00540                                 if ( !tooClose && !tooFar )
00541                                 {//okay too shoot at last pos
00542                                         VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
00543                                         VectorNormalize( dir );
00544                                         vectoangles( dir, angles );
00545 
00546                                         NPCInfo->desiredYaw             = angles[YAW];
00547                                         NPCInfo->desiredPitch   = angles[PITCH];
00548 
00549                                         shoot4 = qtrue;
00550                                         faceEnemy4 = qfalse;
00551                                         return;
00552                                 }
00553                         }
00554                 }
00555         }
00556 }
00557 
00558 void NPC_GM_StartLaser( void )
00559 {
00560         if ( !NPC->lockCount )
00561         {//haven't already started a laser attack
00562                 //warm up for the beam attack
00563 #if 0
00564                 NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_RAISEWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00565 #endif
00566                 TIMER_Set( NPC, "beamDelay", NPC->client->ps.torsoTimer );
00567                 TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer+3000 );
00568                 NPC->lockCount = 1;
00569                 //turn on warmup effect
00570                 G_PlayEffectID( G_EffectIndex("galak/beam_warmup"), NPC->r.currentOrigin, vec3_origin );
00571                 G_SoundOnEnt( NPC, CHAN_AUTO, "sound/weapons/galak/lasercharge.wav" );
00572         }
00573 }
00574 
00575 void GM_StartGloat( void )
00576 {
00577         NPC->wait = 0;
00578         NPC_SetSurfaceOnOff( NPC, "torso_galakface", TURN_ON );
00579         NPC_SetSurfaceOnOff( NPC, "torso_galakhead", TURN_ON );
00580         NPC_SetSurfaceOnOff( NPC, "torso_eyes_mouth", TURN_ON );
00581         NPC_SetSurfaceOnOff( NPC, "torso_collar", TURN_ON );
00582         NPC_SetSurfaceOnOff( NPC, "torso_galaktorso", TURN_ON );
00583 
00584         NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND2TO1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00585         NPC->client->ps.legsTimer += 500;
00586         NPC->client->ps.torsoTimer += 500;
00587 }
00588 /*
00589 -------------------------
00590 NPC_BSGM_Attack
00591 -------------------------
00592 */
00593 
00594 void NPC_BSGM_Attack( void )
00595 {
00596         //Don't do anything if we're hurt
00597         if ( NPC->painDebounceTime > level.time )
00598         {
00599                 NPC_UpdateAngles( qtrue, qtrue );
00600                 return;
00601         }
00602 
00603 #if 0
00604         //FIXME: if killed enemy, use victory anim
00605         if ( NPC->enemy && NPC->enemy->health <= 0 
00606                 && !NPC->enemy->s.number )
00607         {//my enemy is dead
00608                 if ( NPC->client->ps.torsoAnim == BOTH_STAND2TO1 )
00609                 {
00610                         if ( NPC->client->ps.torsoTimer <= 500 )
00611                         {
00612                                 G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 );
00613                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00614                                 NPC->client->ps.legsTimer += 500;
00615                                 NPC->client->ps.torsoTimer += 500;
00616                         }
00617                 }
00618                 else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1START )
00619                 {
00620                         if ( NPC->client->ps.torsoTimer <= 500 )
00621                         {
00622                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STARTGESTURE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00623                                 NPC->client->ps.legsTimer += 500;
00624                                 NPC->client->ps.torsoTimer += 500;
00625                         }
00626                 }
00627                 else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STARTGESTURE )
00628                 {
00629                         if ( NPC->client->ps.torsoTimer <= 500 )
00630                         {
00631                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00632                                 NPC->client->ps.legsTimer += 500;
00633                                 NPC->client->ps.torsoTimer += 500;
00634                         }
00635                 }
00636                 else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STOP )
00637                 {
00638                         if ( NPC->client->ps.torsoTimer <= 500 )
00639                         {
00640                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00641                                 NPC->client->ps.legsTimer = -1;
00642                                 NPC->client->ps.torsoTimer = -1;
00643                         }
00644                 }
00645                 else if ( NPC->wait )
00646                 {
00647                         if ( TIMER_Done( NPC, "gloatTime" ) )
00648                         {
00649                                 GM_StartGloat();
00650                         }
00651                         else if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared
00652                         {
00653                                 NPCInfo->goalEntity = NPC->enemy;
00654                                 GM_Move();
00655                         }
00656                         else
00657                         {//got there
00658                                 GM_StartGloat();
00659                         }
00660                 }
00661                 NPC_FaceEnemy( qtrue );
00662                 NPC_UpdateAngles( qtrue, qtrue );
00663                 return;
00664         }
00665 #endif
00666 
00667         //If we don't have an enemy, just idle
00668         if ( NPC_CheckEnemyExt(qfalse) == qfalse || !NPC->enemy )
00669         {
00670                 NPC->enemy = NULL;
00671                 NPC_BSGM_Patrol();
00672                 return;
00673         }
00674 
00675         enemyLOS4 = enemyCS4 = qfalse;
00676         move4 = qtrue;
00677         faceEnemy4 = qfalse;
00678         shoot4 = qfalse;
00679         hitAlly4 = qfalse;
00680         VectorClear( impactPos4 );
00681         enemyDist4 = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );
00682 
00683         //if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 ||
00684         //      NPC->client->ps.torsoAnim == BOTH_ATTACK5 )
00685         if (0)
00686         {
00687                 shoot4 = qfalse;
00688                 if ( TIMER_Done( NPC, "smackTime" ) && !NPCInfo->blockedDebounceTime )
00689                 {//time to smack
00690                         //recheck enemyDist4 and InFront
00691                         if ( enemyDist4 < MELEE_DIST_SQUARED && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) )
00692                         {
00693                                 vec3_t  smackDir;
00694                                 VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, smackDir );
00695                                 smackDir[2] += 30;
00696                                 VectorNormalize( smackDir );
00697                                 //hurt them
00698                                 G_Sound( NPC->enemy, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ) );
00699                                 G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->r.currentOrigin, (g_spskill.integer+1)*Q_irand( 5, 10), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); 
00700                                 if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 )
00701                                 {//smackdown
00702                                         int knockAnim = BOTH_KNOCKDOWN1;
00703                                         if ( BG_CrouchAnim( NPC->enemy->client->ps.legsAnim ) )
00704                                         {//knockdown from crouch
00705                                                 knockAnim = BOTH_KNOCKDOWN4;
00706                                         }
00707                                         //throw them
00708                                         smackDir[2] = 1;
00709                                         VectorNormalize( smackDir );
00710                                         G_Throw( NPC->enemy, smackDir, 50 );
00711                                         NPC_SetAnim( NPC->enemy, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00712                                 }
00713                                 else
00714                                 {//uppercut
00715                                         //throw them
00716                                         G_Throw( NPC->enemy, smackDir, 100 );
00717                                         //make them backflip
00718                                         NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_KNOCKDOWN5, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00719                                 }
00720                                 //done with the damage
00721                                 NPCInfo->blockedDebounceTime = 1;
00722                         }
00723                 }
00724         }
00725         else if ( NPC->lockCount ) //already shooting laser
00726         {//sometimes use the laser beam attack, but only after he's taken down our generator
00727                 shoot4 = qfalse;
00728                 if ( NPC->lockCount == 1 )
00729                 {//charging up
00730                         if ( TIMER_Done( NPC, "beamDelay" ) )
00731                         {//time to start the beam
00732                                 int laserAnim;
00733                                 //if ( Q_irand( 0, 1 ) )
00734                                 if (1)
00735                                 {
00736                                         laserAnim = BOTH_ATTACK2;
00737                                 }
00738                                 /*
00739                                 else
00740                                 {
00741                                         laserAnim = BOTH_ATTACK7;
00742                                 }
00743                                 */
00744                                 NPC_SetAnim( NPC, SETANIM_BOTH, laserAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00745                                 TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer + Q_irand( 1000, 3000 ) );
00746                                 //turn on beam effect
00747                                 NPC->lockCount = 2;
00748                                 G_PlayEffectID( G_EffectIndex("galak/trace_beam"), NPC->r.currentOrigin, vec3_origin );
00749                                 NPC->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" );
00750                                 if ( !NPCInfo->coverTarg )
00751                                 {//for moving looping sound at end of trace
00752                                         NPCInfo->coverTarg = G_Spawn();
00753                                         if ( NPCInfo->coverTarg )
00754                                         {
00755                                                 G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint );
00756                                                 NPCInfo->coverTarg->r.svFlags |= SVF_BROADCAST;
00757                                                 NPCInfo->coverTarg->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" );
00758                                         }
00759                                 }
00760                         }
00761                 }
00762                 else
00763                 {//in the actual attack now
00764                         if ( NPC->client->ps.torsoTimer <= 0 )
00765                         {//attack done!
00766                                 NPC->lockCount = 0;
00767                                 G_FreeEntity( NPCInfo->coverTarg );
00768                                 NPC->s.loopSound = 0;
00769 #if 0
00770                                 NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_DROPWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00771 #endif
00772                                 TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer );
00773                         }
00774                         else
00775                         {//attack still going
00776                                 //do the trace and damage
00777                                 trace_t trace;
00778                                 vec3_t  end, mins={-3,-3,-3}, maxs={3,3,3};
00779                                 VectorMA( NPC->client->renderInfo.muzzlePoint, 1024, NPC->client->renderInfo.muzzleDir, end );
00780                                 trap_Trace( &trace, NPC->client->renderInfo.muzzlePoint, mins, maxs, end, NPC->s.number, MASK_SHOT );
00781                                 if ( trace.allsolid || trace.startsolid )
00782                                 {//oops, in a wall
00783                                         if ( NPCInfo->coverTarg )
00784                                         {
00785                                                 G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint );
00786                                         }
00787                                 }
00788                                 else
00789                                 {//clear
00790                                         if ( trace.fraction < 1.0f )
00791                                         {//hit something
00792                                                 gentity_t *traceEnt = &g_entities[trace.entityNum];
00793                                                 if ( traceEnt && traceEnt->takedamage )
00794                                                 {//damage it
00795                                                         G_SoundAtLoc( trace.endpos, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) );
00796                                                         G_Damage( traceEnt, NPC, NPC, NPC->client->renderInfo.muzzleDir, trace.endpos, 10, 0, MOD_UNKNOWN );
00797                                                 }
00798                                         }
00799                                         if ( NPCInfo->coverTarg )
00800                                         {
00801                                                 G_SetOrigin( NPCInfo->coverTarg, trace.endpos );
00802                                         }
00803                                         if ( !Q_irand( 0, 5 ) )
00804                                         {
00805                                                 G_SoundAtLoc( trace.endpos, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) );
00806                                         }
00807                                 }
00808                         }
00809                 }
00810         }
00811         else 
00812         {//Okay, we're not in a special attack, see if we should switch weapons or start a special attack
00813                 /*
00814                 if ( NPC->s.weapon == WP_REPEATER 
00815                         && !(NPCInfo->scriptFlags & SCF_ALT_FIRE)//using rapid-fire
00816                         && NPC->enemy->s.weapon == WP_SABER //enemy using saber
00817                         && NPC->client && (NPC->client->ps.saberEventFlags&SEF_DEFLECTED)
00818                         && !Q_irand( 0, 50 ) )
00819                 {//he's deflecting my shots, switch to the laser or the lob fire for a while
00820                         TIMER_Set( NPC, "noRapid", Q_irand( 2000, 6000 ) );
00821                         NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00822                         NPC->alt_fire = qtrue;
00823                         if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && (Q_irand( 0, 1 )||enemyDist4 < MAX_LOB_DIST_SQUARED) )
00824                         {//shield down, use laser
00825                                 NPC_GM_StartLaser();
00826                         }
00827                 }
00828                 else*/
00829                 if (// !NPC->client->ps.powerups[PW_GALAK_SHIELD] 
00830                         1 //rwwFIXMEFIXME: just act like the shield is down til the effects and stuff are done
00831                         && enemyDist4 < MELEE_DIST_SQUARED 
00832                         && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) 
00833                         && NPC->enemy->localAnimIndex <= 1 )//within 80 and in front
00834                 {//our shield is down, and enemy within 80, if very close, use melee attack to slap away
00835                         if ( TIMER_Done( NPC, "attackDelay" ) )
00836                         {
00837                                 //animate me
00838                                 int swingAnim = BOTH_ATTACK1;
00839 #if 0
00840                                 if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH )
00841                                 {//generator down, use random melee
00842                                         swingAnim = Q_irand( BOTH_ATTACK4, BOTH_ATTACK5 );//smackdown or uppercut
00843                                 }
00844                                 else
00845                                 {//always knock-away
00846                                         swingAnim = BOTH_ATTACK5;//uppercut
00847                                 }
00848 #endif
00849                                 //FIXME: swing sound
00850                                 NPC_SetAnim( NPC, SETANIM_BOTH, swingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00851                                 TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer + Q_irand( 1000, 3000 ) );
00852                                 //delay the hurt until the proper point in the anim
00853                                 TIMER_Set( NPC, "smackTime", 600 );
00854                                 NPCInfo->blockedDebounceTime = 0;
00855                                 //FIXME: say something?
00856                         }
00857                 }
00858                 else if ( !NPC->lockCount && NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH
00859                         && TIMER_Done( NPC, "attackDelay" )
00860                         && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f )
00861                         && ((!Q_irand( 0, 10*(2-g_spskill.integer))&& enemyDist4 > MIN_LOB_DIST_SQUARED&& enemyDist4 < MAX_LOB_DIST_SQUARED)
00862                                 ||(!TIMER_Done( NPC, "noLob" )&&!TIMER_Done( NPC, "noRapid" ))) 
00863                         && NPC->enemy->s.weapon != WP_TURRET )
00864                 {//sometimes use the laser beam attack, but only after he's taken down our generator
00865                         shoot4 = qfalse;
00866                         NPC_GM_StartLaser();
00867                 }
00868                 else if ( enemyDist4 < MIN_LOB_DIST_SQUARED 
00869                         && (NPC->enemy->s.weapon != WP_TURRET || Q_stricmp( "PAS", NPC->enemy->classname ))
00870                         && TIMER_Done( NPC, "noRapid" ) )//256
00871                 {//enemy within 256
00872                         if ( (NPC->client->ps.weapon == WP_REPEATER) && (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
00873                         {//shooting an explosive, but enemy too close, switch to primary fire
00874                                 NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
00875                                 NPC->alt_fire = qfalse;
00876                                 //FIXME: use weap raise & lower anims
00877                                 NPC_ChangeWeapon( WP_REPEATER );
00878                         }
00879                 }
00880                 else if ( (enemyDist4 > MAX_LOB_DIST_SQUARED || (NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname )))
00881                         && TIMER_Done( NPC, "noLob" ) )//448
00882                 {//enemy more than 448 away and we are ready to try lob fire again
00883                         if ( (NPC->client->ps.weapon == WP_REPEATER) && !(NPCInfo->scriptFlags & SCF_ALT_FIRE) )
00884                         {//enemy far enough away to use lobby explosives
00885                                 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00886                                 NPC->alt_fire = qtrue;
00887                                 //FIXME: use weap raise & lower anims
00888                                 NPC_ChangeWeapon( WP_REPEATER );
00889                         }
00890                 }
00891         }
00892 
00893         //can we see our target?
00894         if ( NPC_ClearLOS4( NPC->enemy ) )
00895         {
00896                 NPCInfo->enemyLastSeenTime = level.time;//used here for aim debouncing, not always a clear LOS
00897                 enemyLOS4 = qtrue;
00898 
00899                 if ( NPC->client->ps.weapon == WP_NONE )
00900                 {
00901                         enemyCS4 = qfalse;//not true, but should stop us from firing
00902                         NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon
00903                 }
00904                 else
00905                 {//can we shoot our target?
00906                         if ( ((NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist4 < MIN_LOB_DIST_SQUARED )//256
00907                         {
00908                                 enemyCS4 = qfalse;//not true, but should stop us from firing
00909                                 hitAlly4 = qtrue;//us!
00910                                 //FIXME: if too close, run away!
00911                         }
00912                         else
00913                         {
00914                                 int hit = NPC_ShotEntity( NPC->enemy, impactPos4 );
00915                                 gentity_t *hitEnt = &g_entities[hit];
00916                                 if ( hit == NPC->enemy->s.number 
00917                                         || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
00918                                         || ( hitEnt && hitEnt->takedamage ) )
00919                                 {//can hit enemy or will hit glass or other breakable, so shoot anyway
00920                                         enemyCS4 = qtrue;
00921                                         NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
00922                                         VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
00923                                 }
00924                                 else
00925                                 {//Hmm, have to get around this bastard
00926                                         NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy
00927                                         if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam )
00928                                         {//would hit an ally, don't fire!!!
00929                                                 hitAlly4 = qtrue;
00930                                         }
00931                                         else
00932                                         {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire
00933                                         }
00934                                 }
00935                         }
00936                 }
00937         }
00938         else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) )
00939         {
00940                 int hit;
00941                 gentity_t *hitEnt;
00942 
00943                 if ( TIMER_Done( NPC, "talkDebounce" ) && !Q_irand( 0, 10 ) )
00944                 {
00945                         if ( NPCInfo->enemyCheckDebounceTime < 8 )
00946                         {
00947                                 int speech = -1;
00948                                 switch( NPCInfo->enemyCheckDebounceTime )
00949                                 {
00950                                 case 0:
00951                                 case 1:
00952                                 case 2:
00953                                         speech = EV_CHASE1 + NPCInfo->enemyCheckDebounceTime;
00954                                         break;
00955                                 case 3:
00956                                 case 4:
00957                                 case 5:
00958                                         speech = EV_COVER1 + NPCInfo->enemyCheckDebounceTime-3;
00959                                         break;
00960                                 case 6:
00961                                 case 7:
00962                                         speech = EV_ESCAPING1 + NPCInfo->enemyCheckDebounceTime-6;
00963                                         break;
00964                                 }
00965                                 NPCInfo->enemyCheckDebounceTime++;
00966                                 if ( speech != -1 )
00967                                 {
00968                                         G_AddVoiceEvent( NPC, speech, Q_irand( 3000, 5000 ) );
00969                                         TIMER_Set( NPC, "talkDebounce", Q_irand( 5000, 7000 ) );
00970                                 }
00971                         }
00972                 }
00973 
00974                 NPCInfo->enemyLastSeenTime = level.time;
00975 
00976                 hit = NPC_ShotEntity( NPC->enemy, impactPos4 );
00977                 hitEnt = &g_entities[hit];
00978                 if ( hit == NPC->enemy->s.number 
00979                         || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
00980                         || ( hitEnt && hitEnt->takedamage ) )
00981                 {//can hit enemy or will hit glass or other breakable, so shoot anyway
00982                         enemyCS4 = qtrue;
00983                 }
00984                 else
00985                 {
00986                         faceEnemy4 = qtrue;
00987                         NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
00988                 }
00989         }
00990 
00991         if ( enemyLOS4 )
00992         {
00993                 faceEnemy4 = qtrue;
00994         }
00995         else
00996         {
00997                 if ( !NPCInfo->goalEntity )
00998                 {
00999                         NPCInfo->goalEntity = NPC->enemy;
01000                 }
01001                 if ( NPCInfo->goalEntity == NPC->enemy )
01002                 {//for now, always chase the enemy
01003                         move4 = qtrue;
01004                 }
01005         }
01006         if ( enemyCS4 )
01007         {
01008                 shoot4 = qtrue;
01009                 //NPCInfo->enemyCheckDebounceTime = level.time;//actually used here as a last actual LOS
01010         }
01011         else
01012         {
01013                 if ( !NPCInfo->goalEntity )
01014                 {
01015                         NPCInfo->goalEntity = NPC->enemy;
01016                 }
01017                 if ( NPCInfo->goalEntity == NPC->enemy )
01018                 {//for now, always chase the enemy
01019                         move4 = qtrue;
01020                 }
01021         }
01022 
01023         //Check for movement to take care of
01024         GM_CheckMoveState();
01025 
01026         //See if we should override shooting decision with any special considerations
01027         GM_CheckFireState();
01028 
01029         if ( NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE) && shoot4 && TIMER_Done( NPC, "attackDelay" ) )
01030         {
01031                 vec3_t  muzzle;
01032                 vec3_t  angles;
01033                 vec3_t  target;
01034                 vec3_t velocity = {0,0,0};
01035                 vec3_t mins = {-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE}, maxs = {REPEATER_ALT_SIZE,REPEATER_ALT_SIZE,REPEATER_ALT_SIZE};
01036                 qboolean clearshot;
01037 
01038                 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
01039                 
01040                 VectorCopy( NPC->enemy->r.currentOrigin, target );
01041 
01042                 target[0] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2);
01043                 target[1] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2);
01044                 target[2] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2);
01045 
01046                 //Find the desired angles
01047                 clearshot = WP_LobFire( NPC, muzzle, target, mins, maxs, MASK_SHOT|CONTENTS_LIGHTSABER, 
01048                         velocity, qtrue, NPC->s.number, NPC->enemy->s.number,
01049                         300, 1100, 1500, qtrue );
01050                 if ( VectorCompare( vec3_origin, velocity ) || (!clearshot&&enemyLOS4&&enemyCS4)  )
01051                 {//no clear lob shot and no lob shot that will hit something breakable
01052                         if ( enemyLOS4 && enemyCS4 && TIMER_Done( NPC, "noRapid" ) )
01053                         {//have a clear straight shot, so switch to primary
01054                                 NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
01055                                 NPC->alt_fire = qfalse;
01056                                 NPC_ChangeWeapon( WP_REPEATER );
01057                                 //keep this weap for a bit
01058                                 TIMER_Set( NPC, "noLob", Q_irand( 500, 1000 ) );
01059                         }
01060                         else
01061                         {
01062                                 shoot4 = qfalse;
01063                         }
01064                 }
01065                 else
01066                 {
01067                         vectoangles( velocity, angles );
01068 
01069                         NPCInfo->desiredYaw             = AngleNormalize360( angles[YAW] );
01070                         NPCInfo->desiredPitch   = AngleNormalize360( angles[PITCH] );
01071 
01072                         VectorCopy( velocity, NPC->client->hiddenDir );
01073                         NPC->client->hiddenDist = VectorNormalize ( NPC->client->hiddenDir );
01074                 }
01075         }
01076         else if ( faceEnemy4 )
01077         {//face the enemy
01078                 NPC_FaceEnemy( qtrue );
01079         }
01080 
01081         if ( !TIMER_Done( NPC, "standTime" ) )
01082         {
01083                 move4 = qfalse;
01084         }
01085         if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
01086         {//not supposed to chase my enemies
01087                 if ( NPCInfo->goalEntity == NPC->enemy )
01088                 {//goal is my entity, so don't move
01089                         move4 = qfalse;
01090                 }
01091         }
01092 
01093         if ( move4 && !NPC->lockCount )
01094         {//move toward goal
01095                 if ( NPCInfo->goalEntity 
01096                         /*&& NPC->client->ps.legsAnim != BOTH_ALERT1
01097                         && NPC->client->ps.legsAnim != BOTH_ATTACK2 
01098                         && NPC->client->ps.legsAnim != BOTH_ATTACK4
01099                         && NPC->client->ps.legsAnim != BOTH_ATTACK5 
01100                         && NPC->client->ps.legsAnim != BOTH_ATTACK7*/ )
01101                 {
01102                         move4 = GM_Move();
01103                 }
01104                 else
01105                 {
01106                         move4 = qfalse;
01107                 }
01108         }
01109 
01110         if ( !TIMER_Done( NPC, "flee" ) )
01111         {//running away
01112                 faceEnemy4 = qfalse;
01113         }
01114 
01115         //FIXME: check scf_face_move_dir here?
01116 
01117         if ( !faceEnemy4 )
01118         {//we want to face in the dir we're running
01119                 if ( !move4 )
01120                 {//if we haven't moved, we should look in the direction we last looked?
01121                         VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles );
01122                 }
01123                 if ( move4 )
01124                 {//don't run away and shoot
01125                         NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
01126                         NPCInfo->desiredPitch = 0;
01127                         shoot4 = qfalse;
01128                 }
01129         }
01130         NPC_UpdateAngles( qtrue, qtrue );
01131 
01132         if ( NPCInfo->scriptFlags & SCF_DONT_FIRE )
01133         {
01134                 shoot4 = qfalse;
01135         }
01136 
01137         if ( NPC->enemy && NPC->enemy->enemy )
01138         {
01139                 if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER )
01140                 {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH)
01141                         shoot4 = qfalse;
01142                 }
01143         }
01144         //FIXME: don't shoot right away!
01145         if ( shoot4 )
01146         {//try to shoot if it's time
01147                 if ( TIMER_Done( NPC, "attackDelay" ) )
01148                 {
01149                         if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
01150                         {
01151                                 WeaponThink( qtrue );
01152                         }
01153                 }
01154         }
01155 
01156         //also:
01157         if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) )
01158         {//crush turrets
01159                 if ( G_BoundsOverlap( NPC->r.absmin, NPC->r.absmax, NPC->enemy->r.absmin, NPC->enemy->r.absmax ) )
01160                 {//have to do this test because placed turrets are not solid to NPCs (so they don't obstruct navigation)
01161                         //if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 )
01162                         if (0)
01163                         {
01164                                 NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME;
01165                                 G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->r.currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); 
01166                         }
01167                         else
01168                         {
01169                                 G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->r.currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); 
01170                         }
01171                 }
01172         }
01173         else if ( NPCInfo->touchedByPlayer != NULL && NPCInfo->touchedByPlayer == NPC->enemy )
01174         {//touched enemy
01175                 //if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 )
01176                 if (0)
01177                 {//zap him!
01178                         vec3_t  smackDir;
01179 
01180                         //animate me
01181 #if 0
01182                         NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK6, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
01183 #endif
01184                         TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer );
01185                         TIMER_Set( NPC, "standTime", NPC->client->ps.legsTimer );
01186                         //FIXME: debounce this?
01187                         NPCInfo->touchedByPlayer = NULL;
01188                         //FIXME: some shield effect?
01189                         NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME;
01190 
01191                         VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, smackDir );
01192                         smackDir[2] += 30;
01193                         VectorNormalize( smackDir );
01194                         G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->r.currentOrigin, (g_spskill.integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); 
01195                         //throw them
01196                         G_Throw( NPC->enemy, smackDir, 100 );
01197                         //NPC->enemy->s.powerups |= ( 1 << PW_SHOCKED );
01198                         if ( NPC->enemy->client )
01199                         {
01200                         //      NPC->enemy->client->ps.powerups[PW_SHOCKED] = level.time + 1000;
01201                                 NPC->enemy->client->ps.electrifyTime = level.time + 1000;
01202                         }
01203                         //stop any attacks
01204                         ucmd.buttons = 0;
01205                 }
01206         }
01207 
01208         if ( NPCInfo->movementSpeech < 3 && NPCInfo->blockedSpeechDebounceTime <= level.time )
01209         {
01210                 if ( NPC->enemy && NPC->enemy->health > 0 && NPC->enemy->painDebounceTime > level.time )
01211                 {
01212                         if ( NPC->enemy->health < 50 && NPCInfo->movementSpeech == 2 )
01213                         {
01214                                 G_AddVoiceEvent( NPC, EV_ANGER2, Q_irand( 2000, 4000 ) );
01215                                 NPCInfo->movementSpeech = 3;
01216                         }
01217                         else if ( NPC->enemy->health < 75 && NPCInfo->movementSpeech == 1 )
01218                         {
01219                                 G_AddVoiceEvent( NPC, EV_ANGER1, Q_irand( 2000, 4000 ) );
01220                                 NPCInfo->movementSpeech = 2;
01221                         }
01222                         else if ( NPC->enemy->health < 100 && NPCInfo->movementSpeech == 0 )
01223                         {
01224                                 G_AddVoiceEvent( NPC, EV_ANGER3, Q_irand( 2000, 4000 ) );
01225                                 NPCInfo->movementSpeech = 1;
01226                         }
01227                 }
01228         }
01229 }
01230 
01231 void NPC_BSGM_Default( void )
01232 {
01233         if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
01234         {
01235                 WeaponThink( qtrue );
01236         }
01237         
01238         if ( NPC->client->ps.stats[STAT_ARMOR] <= 0 )
01239         {//armor gone
01240         //      if ( !NPCInfo->investigateDebounceTime )
01241                 if (0)
01242                 {//start regenerating the armor
01243                         NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_OFF );
01244                         NPC->flags &= ~FL_SHIELDED;//no more reflections
01245                         VectorSet( NPC->r.mins, -20, -20, -24 );
01246                         VectorSet( NPC->r.maxs, 20, 20, 64 );
01247                         NPC->client->ps.crouchheight = NPC->client->ps.standheight = 64;
01248                         if ( NPC->locationDamage[HL_GENERIC1] < GENERATOR_HEALTH )
01249                         {//still have the generator bolt-on
01250                                 if ( NPCInfo->investigateCount < 12 )
01251                                 {
01252                                         NPCInfo->investigateCount++;
01253                                 }
01254                                 NPCInfo->investigateDebounceTime = level.time + (NPCInfo->investigateCount * 5000);
01255                         }
01256                 }
01257                 else if ( NPCInfo->investigateDebounceTime < level.time )
01258                 {//armor regenerated, turn shield back on
01259                         //do a trace and make sure we can turn this back on?
01260                         trace_t tr;
01261                         trap_Trace( &tr, NPC->r.currentOrigin, shieldMins, shieldMaxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask );
01262                         if ( !tr.startsolid )
01263                         {
01264                                 VectorCopy( shieldMins, NPC->r.mins );
01265                                 VectorCopy( shieldMaxs, NPC->r.maxs );
01266                                 NPC->client->ps.crouchheight = NPC->client->ps.standheight = shieldMaxs[2];
01267                                 NPC->client->ps.stats[STAT_ARMOR] = GALAK_SHIELD_HEALTH;
01268                                 NPCInfo->investigateDebounceTime = 0;
01269                                 NPC->flags |= FL_SHIELDED;//reflect normal shots
01270                         //      NPC->fx_time = level.time;
01271                                 NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_ON );
01272                         }
01273                 }
01274         }
01275         /*
01276         if ( NPC->client->ps.stats[STAT_ARMOR] > 0 )
01277         {//armor present
01278                 NPC->client->ps.powerups[PW_GALAK_SHIELD] = Q3_INFINITE;//temp, for effect
01279                 NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_ON );
01280         }
01281         else
01282         {
01283                 NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_OFF );
01284         }
01285         */
01286         //rwwFIXMEFIXME: Allow this stuff, and again, going to have to let the client know about it.
01287         //Maybe a surface-off bitflag of some sort in the entity state?
01288 
01289         if( !NPC->enemy )
01290         {//don't have an enemy, look for one
01291                 NPC_BSGM_Patrol();
01292         }
01293         else //if ( NPC->enemy )
01294         {//have an enemy
01295                 NPC_BSGM_Attack();
01296         }
01297 }