codemp/game/NPC_AI_Seeker.c File Reference

#include "b_local.h"
#include "g_nav.h"

Go to the source code of this file.

Defines

#define VELOCITY_DECAY   0.7f
#define MIN_MELEE_RANGE   320
#define MIN_MELEE_RANGE_SQR   ( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
#define MIN_DISTANCE   80
#define MIN_DISTANCE_SQR   ( MIN_DISTANCE * MIN_DISTANCE )
#define SEEKER_STRAFE_VEL   100
#define SEEKER_STRAFE_DIS   200
#define SEEKER_UPWARD_PUSH   32
#define SEEKER_FORWARD_BASE_SPEED   10
#define SEEKER_FORWARD_MULTIPLIER   2
#define SEEKER_SEEK_RADIUS   1024

Functions

void Boba_FireDecide (void)
void Seeker_Strafe (void)
void NPC_Seeker_Precache (void)
void NPC_Seeker_Pain (gentity_t *self, gentity_t *attacker, int damage)
void Seeker_MaintainHeight (void)
void Seeker_Hunt (qboolean visible, qboolean advance)
void Seeker_Fire (void)
void Seeker_Ranged (qboolean visible, qboolean advance)
void Seeker_Attack (void)
void Seeker_FindEnemy (void)
void Seeker_FollowOwner (void)
void NPC_BSSeeker_Default (void)


Define Documentation

#define MIN_DISTANCE   80
 

Definition at line 13 of file NPC_AI_Seeker.c.

#define MIN_DISTANCE_SQR   ( MIN_DISTANCE * MIN_DISTANCE )
 

Definition at line 14 of file NPC_AI_Seeker.c.

#define MIN_MELEE_RANGE   320
 

Definition at line 10 of file NPC_AI_Seeker.c.

#define MIN_MELEE_RANGE_SQR   ( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
 

Definition at line 11 of file NPC_AI_Seeker.c.

#define SEEKER_FORWARD_BASE_SPEED   10
 

Definition at line 20 of file NPC_AI_Seeker.c.

Referenced by Seeker_Hunt().

#define SEEKER_FORWARD_MULTIPLIER   2
 

Definition at line 21 of file NPC_AI_Seeker.c.

Referenced by Seeker_Hunt().

#define SEEKER_SEEK_RADIUS   1024
 

Definition at line 23 of file NPC_AI_Seeker.c.

Referenced by Seeker_FindEnemy().

#define SEEKER_STRAFE_DIS   200
 

Definition at line 17 of file NPC_AI_Seeker.c.

Referenced by Seeker_Strafe().

#define SEEKER_STRAFE_VEL   100
 

Definition at line 16 of file NPC_AI_Seeker.c.

Referenced by Seeker_Strafe().

#define SEEKER_UPWARD_PUSH   32
 

Definition at line 18 of file NPC_AI_Seeker.c.

Referenced by Seeker_Strafe().

#define VELOCITY_DECAY   0.7f
 

Definition at line 8 of file NPC_AI_Seeker.c.


Function Documentation

void Boba_FireDecide void   ) 
 

TIMER_Done( NPC, "stick" ) ||

Definition at line 481 of file NPC_AI_Jedi.c.

References AngleVectors(), BG_FlippingAnim(), Boba_ChangeWeapon(), Boba_DoFlameThrower(), Boba_FlyStart(), gNPC_t::burstMax, gNPC_t::burstMean, gNPC_t::burstMin, gNPC_t::burstSpacing, BUTTON_ALT_ATTACK, BUTTON_ATTACK, usercmd_s::buttons, CalcEntitySpot(), gentity_s::client, entityShared_t::currentOrigin, gNPC_t::desiredPitch, gNPC_t::desiredYaw, DotProduct, trace_t::endpos, gentity_s::enemy, enemyDist, gNPC_t::enemyLastSeenLocation, gNPC_t::enemyLastSeenTime, gclient_s::enemyTeam, ENTITYNUM_NONE, playerState_s::fd, forcedata_s::forceJumpZStart, g_entities, gentity_t, playerState_s::groundEntityNum, gNPC_t::group, gentity_s::health, AIGroupInfo_s::lastSeenEnemyTime, playerState_s::legsAnim, level, MASK_SHOT, clientPersistant_t::maxHealth, MIN_ROCKET_DIST_SQUARED, NPC, NPC_ChangeWeapon(), NPC_ClearLOS4(), NPC_ShotEntity(), NPC_UpdateAngles(), NPCInfo, NULL, entityState_s::number, gclient_s::pers, PITCH, gclient_s::playerTeam, gclient_s::ps, Q_irand(), qboolean, qfalse, qtrue, gentity_s::r, gentity_s::s, SCF_ALT_FIRE, SCF_FIRE_WEAPON, gNPC_t::scriptFlags, SPOT_HEAD, SVF_GLASS_BRUSH, entityShared_t::svFlags, gentity_s::takedamage, level_locals_t::time, TIMER_Done(), TIMER_Set(), trap_InPVS(), trap_Trace(), ucmd, vec3_origin, vec3_t, vectoangles(), VectorClear, VectorCopy, VectorMA, VectorNormalize(), VectorSubtract, playerState_s::viewangles, playerState_s::weapon, entityState_s::weapon, WeaponThink(), playerState_s::weaponTime, WP_BLASTER, WP_DET_PACK, WP_DISRUPTOR, WP_EMPLACED_GUN, WP_FLECHETTE, WP_NONE, WP_REPEATER, WP_ROCKET_LAUNCHER, WP_SABER, WP_THERMAL, WP_TRIP_MINE, and YAW.

Referenced by NPC_BSSeeker_Default().

00482 {
00483         qboolean enemyLOS = qfalse;
00484         qboolean enemyCS = qfalse;
00485         qboolean enemyInFOV = qfalse;
00486         //qboolean move = qtrue;
00487         qboolean faceEnemy = qfalse;
00488         qboolean shoot = qfalse;
00489         qboolean hitAlly = qfalse;
00490         vec3_t  impactPos;
00491         float   enemyDist;
00492         float   dot;
00493         vec3_t  enemyDir, shootDir;
00494 
00495         if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE 
00496                 && NPC->client->ps.fd.forceJumpZStart
00497                 && !BG_FlippingAnim( NPC->client->ps.legsAnim )
00498                 && !Q_irand( 0, 10 ) )
00499         {//take off
00500                 Boba_FlyStart( NPC );
00501         }
00502 
00503         if ( !NPC->enemy )
00504         {
00505                 return;
00506         }
00507 
00508         /*
00509         if ( NPC->enemy->enemy != NPC && NPC->health == NPC->client->pers.maxHealth )
00510         {
00511                 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00512                 Boba_ChangeWeapon( WP_DISRUPTOR );
00513         }
00514         else */if ( NPC->enemy->s.weapon == WP_SABER )
00515         {
00516                 NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
00517                 Boba_ChangeWeapon( WP_ROCKET_LAUNCHER );
00518         }
00519         else
00520         {
00521                 if ( NPC->health < NPC->client->pers.maxHealth*0.5f )
00522                 {
00523                         NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00524                         Boba_ChangeWeapon( WP_BLASTER );
00525                         NPCInfo->burstMin = 3;
00526                         NPCInfo->burstMean = 12;
00527                         NPCInfo->burstMax = 20;
00528                         NPCInfo->burstSpacing = Q_irand( 300, 750 );//attack debounce
00529                 }
00530                 else
00531                 {
00532                         NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
00533                         Boba_ChangeWeapon( WP_BLASTER );
00534                 }
00535         }
00536 
00537         VectorClear( impactPos );
00538         enemyDist = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );
00539 
00540         VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir );
00541         VectorNormalize( enemyDir );
00542         AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL );
00543         dot = DotProduct( enemyDir, shootDir );
00544         if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 )
00545         {//enemy is in front of me or they're very close and not behind me
00546                 enemyInFOV = qtrue;
00547         }
00548 
00549         if ( (enemyDist < (128*128)&&enemyInFOV) || !TIMER_Done( NPC, "flameTime" ) )
00550         {//flamethrower
00551                 Boba_DoFlameThrower( NPC );
00552                 enemyCS = qfalse;
00553                 shoot = qfalse;
00554                 NPCInfo->enemyLastSeenTime = level.time;
00555                 faceEnemy = qtrue;
00556                 ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);
00557         }
00558         else if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128
00559         {//enemy within 128
00560                 if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) && 
00561                         (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
00562                 {//shooting an explosive, but enemy too close, switch to primary fire
00563                         NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
00564                         //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not...
00565                 }
00566         }
00567         else if ( enemyDist > 65536 )//256 squared
00568         {
00569                 if ( NPC->client->ps.weapon == WP_DISRUPTOR )
00570                 {//sniping... should be assumed
00571                         if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
00572                         {//use primary fire
00573                                 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00574                                 //reset fire-timing variables
00575                                 NPC_ChangeWeapon( WP_DISRUPTOR );
00576                                 NPC_UpdateAngles( qtrue, qtrue );
00577                                 return;
00578                         }
00579                 }
00580         }
00581 
00582         //can we see our target?
00583         if ( TIMER_Done( NPC, "nextAttackDelay" ) && TIMER_Done( NPC, "flameTime" ) )
00584         {
00585                 if ( NPC_ClearLOS4( NPC->enemy ) )
00586                 {
00587                         NPCInfo->enemyLastSeenTime = level.time;
00588                         enemyLOS = qtrue;
00589 
00590                         if ( NPC->client->ps.weapon == WP_NONE )
00591                         {
00592                                 enemyCS = qfalse;//not true, but should stop us from firing
00593                         }
00594                         else
00595                         {//can we shoot our target?
00596                                 if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128
00597                                 {
00598                                         enemyCS = qfalse;//not true, but should stop us from firing
00599                                         hitAlly = qtrue;//us!
00600                                         //FIXME: if too close, run away!
00601                                 }
00602                                 else if ( enemyInFOV )
00603                                 {//if enemy is FOV, go ahead and check for shooting
00604                                         int hit = NPC_ShotEntity( NPC->enemy, impactPos );
00605                                         gentity_t *hitEnt = &g_entities[hit];
00606 
00607                                         if ( hit == NPC->enemy->s.number 
00608                                                 || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
00609                                                 || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) )
00610                                         {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway
00611                                                 enemyCS = qtrue;
00612                                                 //NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
00613                                                 VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
00614                                         }
00615                                         else
00616                                         {//Hmm, have to get around this bastard
00617                                                 //NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy
00618                                                 if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam )
00619                                                 {//would hit an ally, don't fire!!!
00620                                                         hitAlly = qtrue;
00621                                                 }
00622                                                 else
00623                                                 {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire
00624                                                 }
00625                                         }
00626                                 }
00627                                 else
00628                                 {
00629                                         enemyCS = qfalse;//not true, but should stop us from firing
00630                                 }
00631                         }
00632                 }
00633                 else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) )
00634                 {
00635                         NPCInfo->enemyLastSeenTime = level.time;
00636                         faceEnemy = qtrue;
00637                         //NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
00638                 }
00639 
00640                 if ( NPC->client->ps.weapon == WP_NONE )
00641                 {
00642                         faceEnemy = qfalse;
00643                         shoot = qfalse;
00644                 }
00645                 else
00646                 {
00647                         if ( enemyLOS )
00648                         {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
00649                                 faceEnemy = qtrue;
00650                         }
00651                         if ( enemyCS )
00652                         {
00653                                 shoot = qtrue;
00654                         }
00655                 }
00656 
00657                 if ( !enemyCS )
00658                 {//if have a clear shot, always try
00659                         //See if we should continue to fire on their last position
00661                         if ( !hitAlly //we're not going to hit an ally
00662                                 && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS?
00663                                 && NPCInfo->enemyLastSeenTime > 0 )//we've seen the enemy
00664                         {
00665                                 if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )//we have seem the enemy in the last 10 seconds
00666                                 {
00667                                         if ( !Q_irand( 0, 10 ) )
00668                                         {
00669                                                 //Fire on the last known position
00670                                                 vec3_t  muzzle, dir, angles;
00671                                                 qboolean tooClose = qfalse;
00672                                                 qboolean tooFar = qfalse;
00673                                                 float distThreshold;
00674                                                 float dist;
00675 
00676                                                 CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
00677                                                 if ( VectorCompare( impactPos, vec3_origin ) )
00678                                                 {//never checked ShotEntity this frame, so must do a trace...
00679                                                         trace_t tr;
00680                                                         //vec3_t        mins = {-2,-2,-2}, maxs = {2,2,2};
00681                                                         vec3_t  forward, end;
00682                                                         AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL );
00683                                                         VectorMA( muzzle, 8192, forward, end );
00684                                                         trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
00685                                                         VectorCopy( tr.endpos, impactPos );
00686                                                 }
00687 
00688                                                 //see if impact would be too close to me
00689                                                 distThreshold = 16384/*128*128*/;//default
00690                                                 switch ( NPC->s.weapon )
00691                                                 {
00692                                                 case WP_ROCKET_LAUNCHER:
00693                                                 case WP_FLECHETTE:
00694                                                 case WP_THERMAL:
00695                                                 case WP_TRIP_MINE:
00696                                                 case WP_DET_PACK:
00697                                                         distThreshold = 65536/*256*256*/;
00698                                                         break;
00699                                                 case WP_REPEATER:
00700                                                         if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
00701                                                         {
00702                                                                 distThreshold = 65536/*256*256*/;
00703                                                         }
00704                                                         break;
00705                                                 default:
00706                                                         break;
00707                                                 }
00708 
00709                                                 dist = DistanceSquared( impactPos, muzzle );
00710 
00711                                                 if ( dist < distThreshold )
00712                                                 {//impact would be too close to me
00713                                                         tooClose = qtrue;
00714                                                 }
00715                                                 else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ||
00716                                                         (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 ))
00717                                                 {//we've haven't seen them in the last 5 seconds
00718                                                         //see if it's too far from where he is
00719                                                         distThreshold = 65536/*256*256*/;//default
00720                                                         switch ( NPC->s.weapon )
00721                                                         {
00722                                                         case WP_ROCKET_LAUNCHER:
00723                                                         case WP_FLECHETTE:
00724                                                         case WP_THERMAL:
00725                                                         case WP_TRIP_MINE:
00726                                                         case WP_DET_PACK:
00727                                                                 distThreshold = 262144/*512*512*/;
00728                                                                 break;
00729                                                         case WP_REPEATER:
00730                                                                 if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
00731                                                                 {
00732                                                                         distThreshold = 262144/*512*512*/;
00733                                                                 }
00734                                                                 break;
00735                                                         default:
00736                                                                 break;
00737                                                         }
00738                                                         dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation );
00739                                                         if ( dist > distThreshold )
00740                                                         {//impact would be too far from enemy
00741                                                                 tooFar = qtrue;
00742                                                         }
00743                                                 }
00744 
00745                                                 if ( !tooClose && !tooFar )
00746                                                 {//okay too shoot at last pos
00747                                                         VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
00748                                                         VectorNormalize( dir );
00749                                                         vectoangles( dir, angles );
00750 
00751                                                         NPCInfo->desiredYaw             = angles[YAW];
00752                                                         NPCInfo->desiredPitch   = angles[PITCH];
00753 
00754                                                         shoot = qtrue;
00755                                                         faceEnemy = qfalse;
00756                                                 }
00757                                         }
00758                                 }
00759                         }
00760                 }
00761 
00762                 //FIXME: don't shoot right away!
00763                 if ( NPC->client->ps.weaponTime > 0 )
00764                 {
00765                         if ( NPC->s.weapon == WP_ROCKET_LAUNCHER )
00766                         {
00767                                 if ( !enemyLOS || !enemyCS )
00768                                 {//cancel it
00769                                         NPC->client->ps.weaponTime = 0;
00770                                 }
00771                                 else
00772                                 {//delay our next attempt
00773                                         TIMER_Set( NPC, "nextAttackDelay", Q_irand( 500, 1000 ) );
00774                                 }
00775                         }
00776                 }
00777                 else if ( shoot )
00778                 {//try to shoot if it's time
00779                         if ( TIMER_Done( NPC, "nextAttackDelay" ) )
00780                         {
00781                                 if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
00782                                 {
00783                                         WeaponThink( qtrue );
00784                                 }
00785                                 //NASTY
00786                                 if ( NPC->s.weapon == WP_ROCKET_LAUNCHER 
00787                                         && (ucmd.buttons&BUTTON_ATTACK) 
00788                                         && !Q_irand( 0, 3 ) )
00789                                 {//every now and then, shoot a homing rocket
00790                                         ucmd.buttons &= ~BUTTON_ATTACK;
00791                                         ucmd.buttons |= BUTTON_ALT_ATTACK;
00792                                         NPC->client->ps.weaponTime = Q_irand( 500, 1500 );
00793                                 }
00794                         }
00795                 }
00796         }
00797 }

void NPC_BSSeeker_Default void   ) 
 

Definition at line 523 of file NPC_AI_Seeker.c.

References Boba_FireDecide(), CLASS_BOBAFETT, CLASS_SEEKER, gentity_s::client, CON_DISCONNECTED, clientPersistant_t::connected, DAMAGE_NO_PROTECTION, gentity_s::enemy, ENTITYNUM_NONE, G_Damage(), g_entities, gentity_t, gentity_s::health, gentity_s::inuse, MOD_TELEFRAG, NPC, gclient_s::NPC_class, NULL, entityState_s::number, entityShared_t::ownerNum, gclient_s::pers, gentity_s::r, random, gentity_s::random, gentity_s::s, Seeker_Attack(), and Seeker_FollowOwner().

Referenced by NPC_BehaviorSet_Seeker().

00524 {
00525         /*
00526         if ( in_camera )
00527         {
00528                 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00529                 {
00530                         // cameras make me commit suicide....
00531                         G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN );
00532                 }
00533         }
00534         */
00535         //N/A for MP.
00536         if ( NPC->r.ownerNum < ENTITYNUM_NONE )
00537         {
00538                 gentity_t *owner = &g_entities[0];
00539                 if ( owner->health <= 0 
00540                         || (owner->client && owner->client->pers.connected == CON_DISCONNECTED) )
00541                 {//owner is dead or gone
00542                         //remove me
00543                         G_Damage( NPC, NULL, NULL, NULL, NULL, 10000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
00544                         return;
00545                 }
00546         }
00547 
00548         if ( NPC->random == 0.0f )
00549         {
00550                 // used to offset seekers around a circle so they don't occupy the same spot.  This is not a fool-proof method.
00551                 NPC->random = random() * 6.3f; // roughly 2pi
00552         }
00553 
00554         if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse )
00555         {
00556                 if ( NPC->client->NPC_class != CLASS_BOBAFETT && ( NPC->enemy->s.number == 0 || ( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_SEEKER )) )
00557                 {
00558                         //hacked to never take the player as an enemy, even if the player shoots at it
00559                         NPC->enemy = NULL;
00560                 }
00561                 else
00562                 {
00563                         Seeker_Attack();
00564                         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00565                         {
00566                                 Boba_FireDecide();
00567                         }
00568                         return;
00569                 }
00570         }
00571 
00572         // In all other cases, follow the player and look for enemies to take on
00573         Seeker_FollowOwner();
00574 }

void NPC_Seeker_Pain gentity_t self,
gentity_t attacker,
int  damage
 

Definition at line 34 of file NPC_AI_Seeker.c.

References gNPC_t::aiFlags, G_Damage(), gentity_t, MOD_FALLING, gentity_s::NPC, NPC_Pain(), NPCAI_CUSTOM_GRAVITY, NULL, RestoreNPCGlobals(), SaveNPCGlobals(), Seeker_Strafe(), SetNPCGlobals(), and vec3_origin.

Referenced by NPC_PainFunc().

00035 {
00036         if ( !(self->NPC->aiFlags&NPCAI_CUSTOM_GRAVITY ))
00037         {//void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod, int hitLoc=HL_NONE );
00038                 G_Damage( self, NULL, NULL, (float*)vec3_origin, (float*)vec3_origin, 999, 0, MOD_FALLING );
00039         }
00040 
00041         SaveNPCGlobals();
00042         SetNPCGlobals( self );
00043         Seeker_Strafe();
00044         RestoreNPCGlobals();
00045         NPC_Pain( self, attacker, damage );
00046 }

void NPC_Seeker_Precache void   ) 
 

Definition at line 26 of file NPC_AI_Seeker.c.

References G_EffectIndex(), and G_SoundIndex().

Referenced by NPC_SpawnType(), and SP_NPC_Droid_Seeker().

00027 {
00028         G_SoundIndex("sound/chars/seeker/misc/fire.wav");
00029         G_SoundIndex( "sound/chars/seeker/misc/hiss.wav");
00030         G_EffectIndex( "env/small_explode");
00031 }

void Seeker_Attack void   ) 
 

Definition at line 350 of file NPC_AI_Seeker.c.

References CLASS_BOBAFETT, gentity_s::client, entityShared_t::currentOrigin, DistanceHorizontalSquared(), gentity_s::enemy, MIN_DISTANCE_SQR, NPC, gclient_s::NPC_class, NPC_ClearLOS4(), NPCInfo, qboolean, qfalse, gentity_s::r, SCF_CHASE_ENEMIES, gNPC_t::scriptFlags, Seeker_Hunt(), Seeker_MaintainHeight(), and Seeker_Ranged().

Referenced by NPC_BSSeeker_Default().

00351 {
00352         float           distance;
00353         qboolean        visible;
00354         qboolean        advance;
00355 
00356         // Always keep a good height off the ground
00357         Seeker_MaintainHeight();
00358 
00359         // Rate our distance to the target, and our visibilty
00360         distance        = DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );       
00361         visible         = NPC_ClearLOS4( NPC->enemy );
00362         advance         = (qboolean)(distance > MIN_DISTANCE_SQR);
00363 
00364         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00365         {
00366                 advance = (qboolean)(distance>(200.0f*200.0f));
00367         }
00368 
00369         // If we cannot see our target, move to see it
00370         if ( visible == qfalse )
00371         {
00372                 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00373                 {
00374                         Seeker_Hunt( visible, advance );
00375                         return;
00376                 }
00377         }
00378 
00379         Seeker_Ranged( visible, advance );
00380 }

void Seeker_FindEnemy void   ) 
 

Definition at line 383 of file NPC_AI_Seeker.c.

References gentity_s::client, entityShared_t::currentOrigin, DistanceHorizontalSquared(), gentity_s::enemy, g_entities, gentity_t, gentity_s::health, gentity_s::inuse, MAX_GENTITIES, NPC, NPC_ClearLOS4(), NPCTEAM_NEUTRAL, NULL, entityState_s::number, gclient_s::playerTeam, gentity_s::r, random, gentity_s::random, gentity_s::s, SEEKER_SEEK_RADIUS, trap_EntitiesInBox(), vec3_t, VectorScale, and VectorSet.

Referenced by Seeker_FollowOwner().

00384 {
00385         int                     numFound;
00386         float           dis, bestDis = SEEKER_SEEK_RADIUS * SEEKER_SEEK_RADIUS + 1;
00387         vec3_t          mins, maxs;
00388         int                     entityList[MAX_GENTITIES];
00389         gentity_t       *ent, *best = NULL;
00390         int                     i;
00391 
00392         VectorSet( maxs, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS );
00393         VectorScale( maxs, -1, mins );
00394 
00395         numFound = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
00396 
00397         for ( i = 0 ; i < numFound ; i++ ) 
00398         {
00399                 ent = &g_entities[entityList[i]];
00400 
00401                 if ( ent->s.number == NPC->s.number 
00402                         || !ent->client //&& || !ent->NPC 
00403                         || ent->health <= 0 
00404                         || !ent->inuse )
00405                 {
00406                         continue;
00407                 }
00408 
00409                 if ( ent->client->playerTeam == NPC->client->playerTeam || ent->client->playerTeam == NPCTEAM_NEUTRAL ) // don't attack same team or bots
00410                 {
00411                         continue;
00412                 }
00413 
00414                 // try to find the closest visible one
00415                 if ( !NPC_ClearLOS4( ent ))
00416                 {
00417                         continue;
00418                 }
00419 
00420                 dis = DistanceHorizontalSquared( NPC->r.currentOrigin, ent->r.currentOrigin );
00421 
00422                 if ( dis <= bestDis )
00423                 {
00424                         bestDis = dis;
00425                         best = ent;
00426                 }
00427         }
00428 
00429         if ( best )
00430         {
00431                 // used to offset seekers around a circle so they don't occupy the same spot.  This is not a fool-proof method.
00432                 NPC->random = random() * 6.3f; // roughly 2pi
00433 
00434                 NPC->enemy = best;
00435         }
00436 }

void Seeker_Fire void   ) 
 

Definition at line 290 of file NPC_AI_Seeker.c.

References CalcEntitySpot(), gentity_s::classname, gentity_s::clipmask, CONTENTS_LIGHTSABER, CreateMissile(), entityShared_t::currentOrigin, gentity_s::damage, DAMAGE_DEATH_KNOCKBACK, gentity_s::dflags, gentity_s::enemy, ENTITYNUM_NONE, G_EffectIndex(), G_PlayEffectID(), gentity_t, MASK_SHOT, gentity_s::methodOfDeath, MOD_BLASTER, NPC, entityShared_t::ownerNum, qfalse, gentity_s::r, gentity_s::s, SPOT_HEAD, vec3_t, VectorMA, VectorNormalize(), VectorSubtract, entityState_s::weapon, and WP_BLASTER.

Referenced by Seeker_Ranged().

00291 {
00292         vec3_t          dir, enemy_org, muzzle;
00293         gentity_t       *missile;
00294 
00295         CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org );
00296         VectorSubtract( enemy_org, NPC->r.currentOrigin, dir );
00297         VectorNormalize( dir );
00298 
00299         // move a bit forward in the direction we shall shoot in so that the bolt doesn't poke out the other side of the seeker
00300         VectorMA( NPC->r.currentOrigin, 15, dir, muzzle );
00301 
00302         missile = CreateMissile( muzzle, dir, 1000, 10000, NPC, qfalse );
00303 
00304         G_PlayEffectID( G_EffectIndex("blaster/muzzle_flash"), NPC->r.currentOrigin, dir );
00305 
00306         missile->classname = "blaster";
00307         missile->s.weapon = WP_BLASTER;
00308 
00309         missile->damage = 5;
00310         missile->dflags = DAMAGE_DEATH_KNOCKBACK;
00311         missile->methodOfDeath = MOD_BLASTER;
00312         missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
00313         if ( NPC->r.ownerNum < ENTITYNUM_NONE )
00314         {
00315                 missile->r.ownerNum = NPC->r.ownerNum;
00316         }
00317 }

void Seeker_FollowOwner void   ) 
 

Definition at line 439 of file NPC_AI_Seeker.c.

References CHAN_AUTO, CLASS_BOBAFETT, gentity_s::client, cos(), entityShared_t::currentOrigin, DistanceHorizontalSquared(), gentity_s::enemy, gNPC_t::enemyCheckDebounceTime, g_entities, G_Sound(), G_SoundIndex(), gentity_t, gNPC_t::goalEntity, gNPC_t::goalRadius, gclient_s::jetPackTime, level, MIN_DISTANCE_SQR, NPC, gclient_s::NPC_class, NPC_MoveToGoal(), NPC_UpdateAngles(), NPCInfo, entityState_s::owner, gentity_s::parent, gclient_s::ps, qtrue, gentity_s::r, random, gentity_s::random, gentity_s::s, Seeker_FindEnemy(), Seeker_MaintainHeight(), sin(), level_locals_t::time, TIMER_Done(), TIMER_Set(), vec3_t, VectorMA, VectorSubtract, and playerState_s::velocity.

Referenced by NPC_BSSeeker_Default().

00440 {
00441         float   dis, minDistSqr;
00442         vec3_t  pt, dir;
00443         gentity_t       *owner = &g_entities[NPC->s.owner];
00444 
00445         Seeker_MaintainHeight();
00446 
00447         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00448         {
00449                 owner = NPC->enemy;
00450         }
00451         if ( !owner || owner == NPC || !owner->client )
00452         {
00453                 return;
00454         }
00455         //rwwFIXMEFIXME: Care about all clients not just 0
00456         dis     = DistanceHorizontalSquared( NPC->r.currentOrigin, owner->r.currentOrigin );
00457         
00458         minDistSqr = MIN_DISTANCE_SQR;
00459 
00460         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00461         {
00462                 if ( TIMER_Done( NPC, "flameTime" ) )
00463                 {
00464                         minDistSqr = 200*200;
00465                 }
00466         }
00467 
00468         if ( dis < minDistSqr )
00469         {
00470                 // generally circle the player closely till we take an enemy..this is our target point
00471                 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00472                 {
00473                         pt[0] = owner->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 250;
00474                         pt[1] = owner->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 250;
00475                         if ( NPC->client->jetPackTime < level.time )
00476                         {
00477                                 pt[2] = NPC->r.currentOrigin[2] - 64;
00478                         }
00479                         else
00480                         {
00481                                 pt[2] = owner->r.currentOrigin[2] + 200;
00482                         }
00483                 }
00484                 else
00485                 {
00486                         pt[0] = owner->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 56;
00487                         pt[1] = owner->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 56;
00488                         pt[2] = owner->r.currentOrigin[2] + 40;
00489                 }
00490 
00491                 VectorSubtract( pt, NPC->r.currentOrigin, dir );
00492                 VectorMA( NPC->client->ps.velocity, 0.8f, dir, NPC->client->ps.velocity );
00493         }
00494         else
00495         {
00496                 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00497                 {
00498                         if ( TIMER_Done( NPC, "seekerhiss" ))
00499                         {
00500                                 TIMER_Set( NPC, "seekerhiss", 1000 + random() * 1000 );
00501                                 G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
00502                         }
00503                 }
00504 
00505                 // Hey come back!
00506                 NPCInfo->goalEntity = owner;
00507                 NPCInfo->goalRadius = 32;
00508                 NPC_MoveToGoal( qtrue );
00509                 NPC->parent = owner;
00510         }
00511 
00512         if ( NPCInfo->enemyCheckDebounceTime < level.time )
00513         {
00514                 // check twice a second to find a new enemy
00515                 Seeker_FindEnemy();
00516                 NPCInfo->enemyCheckDebounceTime = level.time + 500;
00517         }
00518 
00519         NPC_UpdateAngles( qtrue, qtrue );
00520 }

void Seeker_Hunt qboolean  visible,
qboolean  advance
 

Definition at line 242 of file NPC_AI_Seeker.c.

References gentity_s::client, entityShared_t::currentOrigin, gentity_s::enemy, g_spskill, gNPC_t::goalEntity, gNPC_t::goalRadius, vmCvar_t::integer, level, NPC, NPC_FaceEnemy(), NPC_GetMoveDirection(), NPCInfo, gclient_s::ps, qfalse, qtrue, gentity_s::r, SEEKER_FORWARD_BASE_SPEED, SEEKER_FORWARD_MULTIPLIER, Seeker_Strafe(), gNPC_t::standTime, level_locals_t::time, vec3_t, VectorMA, VectorNormalize(), VectorSubtract, and playerState_s::velocity.

Referenced by Seeker_Attack(), and Seeker_Ranged().

00243 {
00244         float   distance, speed;
00245         vec3_t  forward;
00246 
00247         NPC_FaceEnemy( qtrue );
00248 
00249         // If we're not supposed to stand still, pursue the player
00250         if ( NPCInfo->standTime < level.time )
00251         {
00252                 // Only strafe when we can see the player
00253                 if ( visible )
00254                 {
00255                         Seeker_Strafe();
00256                         return;
00257                 }
00258         }
00259 
00260         // If we don't want to advance, stop here
00261         if ( advance == qfalse )
00262         {
00263                 return;
00264         }
00265 
00266         // Only try and navigate if the player is visible
00267         if ( visible == qfalse )
00268         {
00269                 // Move towards our goal
00270                 NPCInfo->goalEntity = NPC->enemy;
00271                 NPCInfo->goalRadius = 24;
00272 
00273                 // Get our direction from the navigator if we can't see our target
00274                 if ( NPC_GetMoveDirection( forward, &distance ) == qfalse )
00275                 {
00276                         return;
00277                 }
00278         }
00279         else
00280         {
00281                 VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward );
00282                 distance = VectorNormalize( forward );
00283         }
00284 
00285         speed = SEEKER_FORWARD_BASE_SPEED + SEEKER_FORWARD_MULTIPLIER * g_spskill.integer;
00286         VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
00287 }

void Seeker_MaintainHeight void   ) 
 

Definition at line 49 of file NPC_AI_Seeker.c.

References CLASS_BOBAFETT, gentity_s::client, entityShared_t::currentOrigin, gentity_s::enemy, fabs(), flrand(), gentity_t, gNPC_t::goalEntity, gNPC_t::lastGoalEntity, entityShared_t::maxs, NPC, gclient_s::NPC_class, NPC_UpdateAngles(), NPCInfo, NULL, gclient_s::ps, Q_irand(), qtrue, gentity_s::r, TIMER_Done(), TIMER_Set(), ucmd, usercmd_s::upmove, playerState_s::velocity, and VELOCITY_DECAY.

Referenced by Seeker_Attack(), and Seeker_FollowOwner().

00050 {       
00051         float   dif;
00052 
00053         // Update our angles regardless
00054         NPC_UpdateAngles( qtrue, qtrue );
00055 
00056         // If we have an enemy, we should try to hover at or a little below enemy eye level
00057         if ( NPC->enemy )
00058         {
00059                 if (TIMER_Done( NPC, "heightChange" ))
00060                 {
00061                         float difFactor;
00062 
00063                         TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 ));
00064 
00065                         // Find the height difference
00066                         dif = (NPC->enemy->r.currentOrigin[2] +  flrand( NPC->enemy->r.maxs[2]/2, NPC->enemy->r.maxs[2]+8 )) - NPC->r.currentOrigin[2]; 
00067 
00068                         difFactor = 1.0f;
00069                         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00070                         {
00071                                 if ( TIMER_Done( NPC, "flameTime" ) )
00072                                 {
00073                                         difFactor = 10.0f;
00074                                 }
00075                         }
00076 
00077                         // cap to prevent dramatic height shifts
00078                         if ( fabs( dif ) > 2*difFactor )
00079                         {
00080                                 if ( fabs( dif ) > 24*difFactor )
00081                                 {
00082                                         dif = ( dif < 0 ? -24*difFactor : 24*difFactor );
00083                                 }
00084 
00085                                 NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
00086                         }
00087                         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00088                         {
00089                                 NPC->client->ps.velocity[2] *= flrand( 0.85f, 3.0f );
00090                         }
00091                 }
00092         }
00093         else
00094         {
00095                 gentity_t *goal = NULL;
00096 
00097                 if ( NPCInfo->goalEntity )      // Is there a goal?
00098                 {
00099                         goal = NPCInfo->goalEntity;
00100                 }
00101                 else
00102                 {
00103                         goal = NPCInfo->lastGoalEntity;
00104                 }
00105                 if ( goal )
00106                 {
00107                         dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2];
00108 
00109                         if ( fabs( dif ) > 24 )
00110                         {
00111                                 ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
00112                         }
00113                         else
00114                         {
00115                                 if ( NPC->client->ps.velocity[2] )
00116                                 {
00117                                         NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
00118 
00119                                         if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
00120                                         {
00121                                                 NPC->client->ps.velocity[2] = 0;
00122                                         }
00123                                 }
00124                         }
00125                 }
00126         }
00127 
00128         // Apply friction
00129         if ( NPC->client->ps.velocity[0] )
00130         {
00131                 NPC->client->ps.velocity[0] *= VELOCITY_DECAY;
00132 
00133                 if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
00134                 {
00135                         NPC->client->ps.velocity[0] = 0;
00136                 }
00137         }
00138 
00139         if ( NPC->client->ps.velocity[1] )
00140         {
00141                 NPC->client->ps.velocity[1] *= VELOCITY_DECAY;
00142 
00143                 if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
00144                 {
00145                         NPC->client->ps.velocity[1] = 0;
00146                 }
00147         }
00148 }

void Seeker_Ranged qboolean  visible,
qboolean  advance
 

Definition at line 320 of file NPC_AI_Seeker.c.

References CLASS_BOBAFETT, gentity_s::client, gentity_s::count, G_Damage(), MOD_UNKNOWN, NPC, gclient_s::NPC_class, NPCInfo, NULL, Q_irand(), SCF_CHASE_ENEMIES, gNPC_t::scriptFlags, Seeker_Fire(), Seeker_Hunt(), TIMER_Done(), and TIMER_Set().

Referenced by Seeker_Attack().

00321 {
00322         if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00323         {
00324                 if ( NPC->count > 0 )
00325                 {
00326                         if ( TIMER_Done( NPC, "attackDelay" ))  // Attack?
00327                         {
00328                                 TIMER_Set( NPC, "attackDelay", Q_irand( 250, 2500 ));
00329                                 Seeker_Fire();
00330                                 NPC->count--;
00331                         }
00332                 }
00333                 else
00334                 {
00335                         // out of ammo, so let it die...give it a push up so it can fall more and blow up on impact
00336         //              NPC->client->ps.gravity = 900;
00337         //              NPC->svFlags &= ~SVF_CUSTOM_GRAVITY;
00338         //              NPC->client->ps.velocity[2] += 16;
00339                         G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN );
00340                 }
00341         }
00342 
00343         if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00344         {
00345                 Seeker_Hunt( visible, advance );
00346         }
00347 } 

void Seeker_Strafe void   ) 
 

Definition at line 151 of file NPC_AI_Seeker.c.

References AngleVectors(), CHAN_AUTO, CLASS_BOBAFETT, gentity_s::client, crandom, entityShared_t::currentOrigin, trace_t::endpos, gentity_s::enemy, renderInfo_s::eyeAngles, trace_t::fraction, G_Sound(), G_SoundIndex(), level, MASK_SOLID, NPC, gclient_s::NPC_class, NPCInfo, NULL, entityState_s::number, gclient_s::ps, gentity_s::r, rand(), random, gclient_s::renderInfo, gentity_s::s, SEEKER_STRAFE_DIS, SEEKER_STRAFE_VEL, SEEKER_UPWARD_PUSH, gNPC_t::standTime, level_locals_t::time, trap_Trace(), vec3_t, VectorMA, VectorNormalize(), VectorSubtract, and playerState_s::velocity.

Referenced by NPC_Seeker_Pain(), and Seeker_Hunt().

00152 {
00153         int             side;
00154         vec3_t  end, right, dir;
00155         trace_t tr;
00156 
00157         if ( random() > 0.7f || !NPC->enemy || !NPC->enemy->client )
00158         {
00159                 // Do a regular style strafe
00160                 AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
00161 
00162                 // Pick a random strafe direction, then check to see if doing a strafe would be
00163                 //      reasonably valid
00164                 side = ( rand() & 1 ) ? -1 : 1;
00165                 VectorMA( NPC->r.currentOrigin, SEEKER_STRAFE_DIS * side, right, end );
00166 
00167                 trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
00168 
00169                 // Close enough
00170                 if ( tr.fraction > 0.9f )
00171                 {
00172                         float vel = SEEKER_STRAFE_VEL;
00173                         float upPush = SEEKER_UPWARD_PUSH;
00174                         if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00175                         {
00176                                 G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
00177                         }
00178                         else
00179                         {
00180                                 vel *= 3.0f;
00181                                 upPush *= 4.0f;
00182                         }
00183                         VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity );
00184                         // Add a slight upward push
00185                         NPC->client->ps.velocity[2] += upPush;
00186 
00187                         NPCInfo->standTime = level.time + 1000 + random() * 500;
00188                 }
00189         }
00190         else
00191         {
00192                 float stDis;
00193 
00194                 // Do a strafe to try and keep on the side of their enemy
00195                 AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL );
00196 
00197                 // Pick a random side
00198                 side = ( rand() & 1 ) ? -1 : 1;
00199                 stDis = SEEKER_STRAFE_DIS;
00200                 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00201                 {
00202                         stDis *= 2.0f;
00203                 }
00204                 VectorMA( NPC->enemy->r.currentOrigin, stDis * side, right, end );
00205 
00206                 // then add a very small bit of random in front of/behind the player action
00207                 VectorMA( end, crandom() * 25, dir, end );
00208 
00209                 trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
00210 
00211                 // Close enough
00212                 if ( tr.fraction > 0.9f )
00213                 {
00214                         float dis, upPush;
00215 
00216                         VectorSubtract( tr.endpos, NPC->r.currentOrigin, dir );
00217                         dir[2] *= 0.25; // do less upward change
00218                         dis = VectorNormalize( dir );
00219 
00220                         // Try to move the desired enemy side
00221                         VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity );
00222 
00223                         upPush = SEEKER_UPWARD_PUSH;
00224                         if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00225                         {
00226                                 G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
00227                         }
00228                         else
00229                         {
00230                                 upPush *= 4.0f;
00231                         }
00232 
00233                         // Add a slight upward push
00234                         NPC->client->ps.velocity[2] += upPush;
00235 
00236                         NPCInfo->standTime = level.time + 2500 + random() * 500;
00237                 }
00238         }
00239 }