codemp/game/NPC_AI_ImperialProbe.c

Go to the documentation of this file.
00001 #include "b_local.h"
00002 #include "g_nav.h"
00003 
00004 #include "../namespace_begin.h"
00005 gitem_t *BG_FindItemForAmmo( ammo_t ammo );
00006 #include "../namespace_end.h"
00007 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
00008 
00009 //Local state enums
00010 enum
00011 {
00012         LSTATE_NONE = 0,
00013         LSTATE_BACKINGUP,
00014         LSTATE_SPINNING,
00015         LSTATE_PAIN,
00016         LSTATE_DROP
00017 };
00018 
00019 void ImperialProbe_Idle( void );
00020 
00021 void NPC_Probe_Precache(void)
00022 {
00023         int i;
00024 
00025         for ( i = 1; i < 4; i++)
00026         {
00027                 G_SoundIndex( va( "sound/chars/probe/misc/probetalk%d", i ) );
00028         }
00029         G_SoundIndex( "sound/chars/probe/misc/probedroidloop" );
00030         G_SoundIndex("sound/chars/probe/misc/anger1");
00031         G_SoundIndex("sound/chars/probe/misc/fire");
00032 
00033         G_EffectIndex( "chunks/probehead" );
00034         G_EffectIndex( "env/med_explode2" );
00035         G_EffectIndex( "explosions/probeexplosion1");
00036         G_EffectIndex( "bryar/muzzle_flash" );
00037 
00038         RegisterItem( BG_FindItemForAmmo( AMMO_BLASTER ));
00039         RegisterItem( BG_FindItemForWeapon( WP_BRYAR_PISTOL ) );
00040 }
00041 /*
00042 -------------------------
00043 Hunter_MaintainHeight
00044 -------------------------
00045 */
00046 
00047 #define VELOCITY_DECAY  0.85f
00048 
00049 void ImperialProbe_MaintainHeight( void )
00050 {       
00051         float   dif;
00052 //      vec3_t  endPos;
00053 //      trace_t trace;
00054 
00055         // Update our angles regardless
00056         NPC_UpdateAngles( qtrue, qtrue );
00057 
00058         // If we have an enemy, we should try to hover at about enemy eye level
00059         if ( NPC->enemy )
00060         {
00061                 // Find the height difference
00062                 dif = NPC->enemy->r.currentOrigin[2] - NPC->r.currentOrigin[2]; 
00063 
00064                 // cap to prevent dramatic height shifts
00065                 if ( fabs( dif ) > 8 )
00066                 {
00067                         if ( fabs( dif ) > 16 )
00068                         {
00069                                 dif = ( dif < 0 ? -16 : 16 );
00070                         }
00071 
00072                         NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
00073                 }
00074         }
00075         else
00076         {
00077                 gentity_t *goal = NULL;
00078 
00079                 if ( NPCInfo->goalEntity )      // Is there a goal?
00080                 {
00081                         goal = NPCInfo->goalEntity;
00082                 }
00083                 else
00084                 {
00085                         goal = NPCInfo->lastGoalEntity;
00086                 }
00087                 if ( goal )
00088                 {
00089                         dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2];
00090 
00091                         if ( fabs( dif ) > 24 )
00092                         {
00093                                 ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
00094                         }
00095                         else
00096                         {
00097                                 if ( NPC->client->ps.velocity[2] )
00098                                 {
00099                                         NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
00100 
00101                                         if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
00102                                         {
00103                                                 NPC->client->ps.velocity[2] = 0;
00104                                         }
00105                                 }
00106                         }
00107                 }
00108                 // Apply friction
00109                 else if ( NPC->client->ps.velocity[2] )
00110                 {
00111                         NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
00112 
00113                         if ( fabs( NPC->client->ps.velocity[2] ) < 1 )
00114                         {
00115                                 NPC->client->ps.velocity[2] = 0;
00116                         }
00117                 }
00118 
00119                 // Stay at a given height until we take on an enemy
00120 /*              VectorSet( endPos, NPC->r.currentOrigin[0], NPC->r.currentOrigin[1], NPC->r.currentOrigin[2] - 512 );
00121                 trap_Trace( &trace, NPC->r.currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID );
00122 
00123                 if ( trace.fraction != 1.0f )
00124                 {
00125                         float   length = ( trace.fraction * 512 );
00126 
00127                         if ( length < 80 )
00128                         {
00129                                 ucmd.upmove = 32;
00130                         }
00131                         else if ( length > 120 )
00132                         {
00133                                 ucmd.upmove = -32;
00134                         }
00135                         else
00136                         { 
00137                                 if ( NPC->client->ps.velocity[2] )
00138                                 {
00139                                         NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
00140 
00141                                         if ( fabs( NPC->client->ps.velocity[2] ) < 1 )
00142                                         {
00143                                                 NPC->client->ps.velocity[2] = 0;
00144                                         }
00145                                 }
00146                         }
00147                 } */
00148         }
00149 
00150         // Apply friction
00151         if ( NPC->client->ps.velocity[0] )
00152         {
00153                 NPC->client->ps.velocity[0] *= VELOCITY_DECAY;
00154 
00155                 if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
00156                 {
00157                         NPC->client->ps.velocity[0] = 0;
00158                 }
00159         }
00160 
00161         if ( NPC->client->ps.velocity[1] )
00162         {
00163                 NPC->client->ps.velocity[1] *= VELOCITY_DECAY;
00164 
00165                 if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
00166                 {
00167                         NPC->client->ps.velocity[1] = 0;
00168                 }
00169         }
00170 }
00171 
00172 /*
00173 -------------------------
00174 ImperialProbe_Strafe
00175 -------------------------
00176 */
00177 
00178 #define HUNTER_STRAFE_VEL       256
00179 #define HUNTER_STRAFE_DIS       200
00180 #define HUNTER_UPWARD_PUSH      32
00181 
00182 void ImperialProbe_Strafe( void )
00183 {
00184         int             dir;
00185         vec3_t  end, right;
00186         trace_t tr;
00187 
00188         AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
00189 
00190         // Pick a random strafe direction, then check to see if doing a strafe would be
00191         //      reasonable valid
00192         dir = ( rand() & 1 ) ? -1 : 1;
00193         VectorMA( NPC->r.currentOrigin, HUNTER_STRAFE_DIS * dir, right, end );
00194 
00195         trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
00196 
00197         // Close enough
00198         if ( tr.fraction > 0.9f )
00199         {
00200                 VectorMA( NPC->client->ps.velocity, HUNTER_STRAFE_VEL * dir, right, NPC->client->ps.velocity );
00201 
00202                 // Add a slight upward push
00203                 NPC->client->ps.velocity[2] += HUNTER_UPWARD_PUSH;
00204 
00205                 // Set the strafe start time so we can do a controlled roll
00206                 //NPC->fx_time = level.time;
00207                 NPCInfo->standTime = level.time + 3000 + random() * 500;
00208         }
00209 }
00210 
00211 /*
00212 -------------------------
00213 ImperialProbe_Hunt
00214 -------------------------`
00215 */
00216 
00217 #define HUNTER_FORWARD_BASE_SPEED       10
00218 #define HUNTER_FORWARD_MULTIPLIER       5
00219 
00220 void ImperialProbe_Hunt( qboolean visible, qboolean advance )
00221 {
00222         float   distance, speed;
00223         vec3_t  forward;
00224 
00225         NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
00226 
00227         //If we're not supposed to stand still, pursue the player
00228         if ( NPCInfo->standTime < level.time )
00229         {
00230                 // Only strafe when we can see the player
00231                 if ( visible )
00232                 {
00233                         ImperialProbe_Strafe();
00234                         return;
00235                 }
00236         }
00237 
00238         //If we don't want to advance, stop here
00239         if ( advance == qfalse )
00240                 return;
00241 
00242         //Only try and navigate if the player is visible
00243         if ( visible == qfalse )
00244         {
00245                 // Move towards our goal
00246                 NPCInfo->goalEntity = NPC->enemy;
00247                 NPCInfo->goalRadius = 12;
00248 
00249                 //Get our direction from the navigator if we can't see our target
00250                 if ( NPC_GetMoveDirection( forward, &distance ) == qfalse )
00251                         return;
00252         }
00253         else
00254         {
00255                 VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward );
00256                 distance = VectorNormalize( forward );
00257         }
00258 
00259         speed = HUNTER_FORWARD_BASE_SPEED + HUNTER_FORWARD_MULTIPLIER * g_spskill.integer;
00260         VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
00261 }
00262 
00263 /*
00264 -------------------------
00265 ImperialProbe_FireBlaster
00266 -------------------------
00267 */
00268 void ImperialProbe_FireBlaster(void)
00269 {
00270         vec3_t  muzzle1,enemy_org1,delta1,angleToEnemy1;
00271         static  vec3_t  forward, vright, up;
00272         static  vec3_t  muzzle;
00273         int genBolt1;
00274         gentity_t       *missile;
00275         mdxaBone_t      boltMatrix;
00276 
00277         genBolt1 = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash");
00278 
00279         //FIXME: use {0, NPC->client->ps.legsYaw, 0}
00280         trap_G2API_GetBoltMatrix( NPC->ghoul2, 0, 
00281                                 genBolt1,
00282                                 &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time,
00283                                 NULL, NPC->modelScale );
00284 
00285         BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle1 );
00286 
00287         G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle1, vec3_origin );
00288 
00289         G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/probe/misc/fire" ));
00290 
00291         if (NPC->health)
00292         {
00293                 CalcEntitySpot( NPC->enemy, SPOT_CHEST, enemy_org1 );
00294                 enemy_org1[0]+= Q_irand(0,10);
00295                 enemy_org1[1]+= Q_irand(0,10);
00296                 VectorSubtract (enemy_org1, muzzle1, delta1);
00297                 vectoangles ( delta1, angleToEnemy1 );
00298                 AngleVectors (angleToEnemy1, forward, vright, up);
00299         }
00300         else
00301         {
00302                 AngleVectors (NPC->r.currentAngles, forward, vright, up);
00303         }
00304 
00305         missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC, qfalse );
00306 
00307         missile->classname = "bryar_proj";
00308         missile->s.weapon = WP_BRYAR_PISTOL;
00309 
00310         if ( g_spskill.integer <= 1 )
00311         {
00312                 missile->damage = 5;
00313         }
00314         else 
00315         {
00316                 missile->damage = 10;
00317         }
00318 
00319 
00320         missile->dflags = DAMAGE_DEATH_KNOCKBACK;
00321         missile->methodOfDeath = MOD_UNKNOWN;
00322         missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
00323 
00324 }
00325 
00326 /*
00327 -------------------------
00328 ImperialProbe_Ranged
00329 -------------------------
00330 */
00331 void ImperialProbe_Ranged( qboolean visible, qboolean advance )
00332 {
00333         int     delay_min,delay_max;
00334 
00335         if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack?
00336         {
00337 
00338                 if ( g_spskill.integer == 0 )
00339                 {
00340                         delay_min = 500;
00341                         delay_max = 3000;
00342                 }
00343                 else if ( g_spskill.integer > 1 )
00344                 {
00345                         delay_min = 500;
00346                         delay_max = 2000;
00347                 }
00348                 else
00349                 {
00350                         delay_min = 300;
00351                         delay_max = 1500;
00352                 }
00353 
00354                 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) );
00355                 ImperialProbe_FireBlaster();
00356 //              ucmd.buttons |= BUTTON_ATTACK;
00357         }
00358 
00359         if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00360         {
00361                 ImperialProbe_Hunt( visible, advance );
00362         }
00363 }
00364 
00365 /*
00366 -------------------------
00367 ImperialProbe_AttackDecision
00368 -------------------------
00369 */
00370 
00371 #define MIN_MELEE_RANGE         320
00372 #define MIN_MELEE_RANGE_SQR     ( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
00373 
00374 #define MIN_DISTANCE            128
00375 #define MIN_DISTANCE_SQR        ( MIN_DISTANCE * MIN_DISTANCE )
00376 
00377 void ImperialProbe_AttackDecision( void )
00378 {
00379         float           distance;       
00380         qboolean        visible;
00381         qboolean        advance;
00382 
00383         // Always keep a good height off the ground
00384         ImperialProbe_MaintainHeight();
00385 
00386         //randomly talk
00387         if ( TIMER_Done(NPC,"patrolNoise") )
00388         {
00389                 if (TIMER_Done(NPC,"angerNoise"))
00390                 {
00391                         G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) );
00392 
00393                         TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) );
00394                 }
00395         }
00396 
00397         // If we don't have an enemy, just idle
00398         if ( NPC_CheckEnemyExt(qfalse) == qfalse )
00399         {
00400                 ImperialProbe_Idle();
00401                 return;
00402         }
00403 
00404         NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL);
00405 
00406         // Rate our distance to the target, and our visibilty
00407         distance        = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); 
00408         visible         = NPC_ClearLOS4( NPC->enemy );
00409         advance         = (qboolean)(distance > MIN_DISTANCE_SQR);
00410 
00411         // If we cannot see our target, move to see it
00412         if ( visible == qfalse )
00413         {
00414                 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00415                 {
00416                         ImperialProbe_Hunt( visible, advance );
00417                         return;
00418                 }
00419         }
00420 
00421         // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb
00422         NPC_FaceEnemy( qtrue );
00423 
00424         // Decide what type of attack to do
00425         ImperialProbe_Ranged( visible, advance );
00426 }
00427 
00428 /*
00429 -------------------------
00430 NPC_BSDroid_Pain
00431 -------------------------
00432 */
00433 void NPC_Probe_Pain(gentity_t *self, gentity_t *attacker, int damage)
00434 {
00435         float   pain_chance;
00436         gentity_t *other = attacker;
00437         int mod = gPainMOD;
00438         
00439         VectorCopy( self->NPC->lastPathAngles, self->s.angles );
00440 
00441         if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) // demp2 always messes them up real good
00442         {
00443                 vec3_t endPos;
00444                 trace_t trace;
00445 
00446                 VectorSet( endPos, self->r.currentOrigin[0], self->r.currentOrigin[1], self->r.currentOrigin[2] - 128 );
00447                 trap_Trace( &trace, self->r.currentOrigin, NULL, NULL, endPos, self->s.number, MASK_SOLID );
00448 
00449                 if ( trace.fraction == 1.0f || mod == MOD_DEMP2 ) // demp2 always does this
00450                 {
00451                         /*
00452                         if (self->client->clientInfo.headModel != 0)
00453                         {
00454                                 vec3_t origin;
00455 
00456                                 VectorCopy(self->r.currentOrigin,origin);
00457                                 origin[2] +=50;
00458 //                              G_PlayEffect( "small_chunks", origin );
00459                                 G_PlayEffect( "chunks/probehead", origin );
00460                                 G_PlayEffect( "env/med_explode2", origin );
00461                                 self->client->clientInfo.headModel = 0;
00462                                 self->client->moveType = MT_RUNJUMP;
00463                                 self->client->ps.gravity = g_gravity->value*.1;
00464                         }
00465                         */
00466                         
00467                         if ( (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) && other )
00468                         {
00469                                 vec3_t dir;
00470 
00471                                 NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
00472 
00473                                 VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir );
00474                                 VectorNormalize( dir );
00475 
00476                                 VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity );
00477                                 self->client->ps.velocity[2] -= 127;
00478                         }
00479 
00480                         //self->s.powerups |= ( 1 << PW_SHOCKED );
00481                         //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000;
00482                         self->client->ps.electrifyTime = level.time + 3000;
00483 
00484                         self->NPC->localState = LSTATE_DROP;
00485                 } 
00486         }
00487         else
00488         {
00489                 pain_chance = NPC_GetPainChance( self, damage );
00490 
00491                 if ( random() < pain_chance )   // Spin around in pain?
00492                 {
00493                         NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE);
00494                 }       
00495         }
00496 
00497         NPC_Pain( self, attacker, damage );
00498 }
00499 
00500 /*
00501 -------------------------
00502 ImperialProbe_Idle
00503 -------------------------
00504 */
00505 
00506 void ImperialProbe_Idle( void )
00507 {
00508         ImperialProbe_MaintainHeight();
00509 
00510         NPC_BSIdle();
00511 }
00512 
00513 /*
00514 -------------------------
00515 NPC_BSImperialProbe_Patrol
00516 -------------------------
00517 */
00518 void ImperialProbe_Patrol( void )
00519 {
00520         ImperialProbe_MaintainHeight();
00521 
00522         if ( NPC_CheckPlayerTeamStealth() )
00523         {
00524                 NPC_UpdateAngles( qtrue, qtrue );
00525                 return;
00526         }
00527 
00528         //If we have somewhere to go, then do that
00529         if (!NPC->enemy)
00530         {
00531                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL );
00532 
00533                 if ( UpdateGoal() )
00534                 {
00535                         //start loop sound once we move
00536                         NPC->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" );
00537                         ucmd.buttons |= BUTTON_WALKING;
00538                         NPC_MoveToGoal( qtrue );
00539                 }
00540                 //randomly talk
00541                 if (TIMER_Done(NPC,"patrolNoise"))
00542                 {
00543                         G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) );
00544 
00545                         TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
00546                 }
00547         }
00548         else    // He's got an enemy. Make him angry.
00549         {
00550                 G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/probe/misc/anger1" );
00551                 TIMER_Set( NPC, "angerNoise", Q_irand( 2000, 4000 ) );
00552                 //NPCInfo->behaviorState = BS_HUNT_AND_KILL;
00553         }
00554 
00555         NPC_UpdateAngles( qtrue, qtrue );
00556 }
00557 
00558 /*
00559 -------------------------
00560 ImperialProbe_Wait
00561 -------------------------
00562 */
00563 void ImperialProbe_Wait(void)
00564 {
00565         if ( NPCInfo->localState == LSTATE_DROP )
00566         {
00567                 vec3_t endPos;
00568                 trace_t trace;
00569 
00570                 NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->desiredYaw + 25 );
00571 
00572                 VectorSet( endPos, NPC->r.currentOrigin[0], NPC->r.currentOrigin[1], NPC->r.currentOrigin[2] - 32 );
00573                 trap_Trace( &trace, NPC->r.currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID );
00574 
00575                 if ( trace.fraction != 1.0f )
00576                 {
00577                         G_Damage(NPC, NPC->enemy, NPC->enemy, NULL, NULL, 2000, 0,MOD_UNKNOWN); 
00578                 } 
00579         }
00580 
00581         NPC_UpdateAngles( qtrue, qtrue );
00582 }
00583 
00584 /*
00585 -------------------------
00586 NPC_BSImperialProbe_Default
00587 -------------------------
00588 */
00589 void NPC_BSImperialProbe_Default( void )
00590 {
00591 
00592         if ( NPC->enemy )
00593         {
00594                 NPCInfo->goalEntity = NPC->enemy;
00595                 ImperialProbe_AttackDecision();
00596         }
00597         else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
00598         {
00599                 ImperialProbe_Patrol();
00600         }
00601         else if ( NPCInfo->localState == LSTATE_DROP )
00602         {
00603                 ImperialProbe_Wait();
00604         }
00605         else
00606         {
00607                 ImperialProbe_Idle();
00608         }
00609 }