jedioutcast/code/game/AI_Sentry.cpp
2013-04-04 13:07:40 -05:00

565 lines
13 KiB
C++

// leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
#include "g_headers.h"
#include "b_local.h"
#include "g_nav.h"
gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse );
extern gitem_t *FindItemForAmmo( ammo_t ammo );
#define MIN_DISTANCE 256
#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
#define SENTRY_FORWARD_BASE_SPEED 10
#define SENTRY_FORWARD_MULTIPLIER 5
#define SENTRY_VELOCITY_DECAY 0.85f
#define SENTRY_STRAFE_VEL 256
#define SENTRY_STRAFE_DIS 200
#define SENTRY_UPWARD_PUSH 32
#define SENTRY_HOVER_HEIGHT 24
//Local state enums
enum
{
LSTATE_NONE = 0,
LSTATE_ASLEEP,
LSTATE_WAKEUP,
LSTATE_ACTIVE,
LSTATE_POWERING_UP,
LSTATE_ATTACKING,
};
/*
-------------------------
NPC_Sentry_Precache
-------------------------
*/
void NPC_Sentry_Precache(void)
{
G_SoundIndex( "sound/chars/sentry/misc/death.wav" );
G_SoundIndex( "sound/chars/sentry/misc/sentry_pain.mp3" );
G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_open.mp3" );
G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_close.mp3" );
G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp.wav" );
G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp.wav" );
// G_SoundIndex( "sound/chars/sentry/misc/shoot.wav");
for ( int i = 1; i < 4; i++)
{
G_SoundIndex( va( "sound/chars/sentry/misc/talk%d.wav", i ) );
}
G_EffectIndex( "bryar/muzzle_flash");
G_EffectIndex( "env/med_explode");
RegisterItem( FindItemForAmmo( AMMO_BLASTER ));
}
/*
================
sentry_use
================
*/
void sentry_use( gentity_t *self, gentity_t *other, gentity_t *activator)
{
G_ActivateBehavior(self,BSET_USE);
self->flags &= ~FL_SHIELDED;
NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
// self->NPC->localState = LSTATE_WAKEUP;
self->NPC->localState = LSTATE_ACTIVE;
}
/*
-------------------------
NPC_Sentry_Pain
-------------------------
*/
void NPC_Sentry_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod,int hitLoc )
{
NPC_Pain( self, inflictor, other, point, damage, mod );
if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
{
self->NPC->burstCount = 0;
TIMER_Set( self, "attackDelay", Q_irand( 9000, 12000) );
self->flags |= FL_SHIELDED;
NPC_SetAnim( self, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
G_Sound( NPC, G_SoundIndex( "sound/chars/misc/sentry/sentry_pain.mp3" ));
self->NPC->localState = LSTATE_ACTIVE;
}
// You got hit, go after the enemy
// if (self->NPC->localState == LSTATE_ASLEEP)
// {
// G_Sound( self, G_SoundIndex("sound/chars/sentry/misc/shieldsopen.wav"));
//
// self->flags &= ~FL_SHIELDED;
// NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
// self->NPC->localState = LSTATE_WAKEUP;
// }
}
/*
-------------------------
Sentry_Fire
-------------------------
*/
void Sentry_Fire (void)
{
vec3_t muzzle;
static vec3_t forward, vright, up;
gentity_t *missile;
mdxaBone_t boltMatrix;
int bolt;
NPC->flags &= ~FL_SHIELDED;
if ( NPCInfo->localState == LSTATE_POWERING_UP )
{
if ( TIMER_Done( NPC, "powerup" ))
{
NPCInfo->localState = LSTATE_ATTACKING;
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
}
else
{
// can't do anything right now
return;
}
}
else if ( NPCInfo->localState == LSTATE_ACTIVE )
{
NPCInfo->localState = LSTATE_POWERING_UP;
G_Sound( NPC, G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_open.mp3" ));
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
TIMER_Set( NPC, "powerup", 250 );
return;
}
else if ( NPCInfo->localState != LSTATE_ATTACKING )
{
// bad because we are uninitialized
NPCInfo->localState = LSTATE_ACTIVE;
return;
}
// Which muzzle to fire from?
int which = NPCInfo->burstCount % 3;
switch( which )
{
case 0:
bolt = NPC->genericBolt1;
break;
case 1:
bolt = NPC->genericBolt2;
break;
case 2:
default:
bolt = NPC->genericBolt3;
}
gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel,
bolt,
&boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
NULL, NPC->s.modelScale );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle );
AngleVectors( NPC->currentAngles, forward, vright, up );
// G_Sound( NPC, G_SoundIndex("sound/chars/sentry/misc/shoot.wav"));
G_PlayEffect( "bryar/muzzle_flash", muzzle, forward );
missile = CreateMissile( muzzle, forward, 1600, 10000, NPC );
missile->classname = "bryar_proj";
missile->s.weapon = WP_BRYAR_PISTOL;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = MOD_ENERGY;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
NPCInfo->burstCount++;
NPC->attackDebounceTime = level.time + 50;
missile->damage = 5;
// now scale for difficulty
if ( g_spskill->integer == 0 )
{
NPC->attackDebounceTime += 200;
missile->damage = 1;
}
else if ( g_spskill->integer == 1 )
{
NPC->attackDebounceTime += 100;
missile->damage = 3;
}
}
/*
-------------------------
Sentry_MaintainHeight
-------------------------
*/
void Sentry_MaintainHeight( void )
{
float dif;
NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp.wav" );
// Update our angles regardless
NPC_UpdateAngles( qtrue, qtrue );
// If we have an enemy, we should try to hover at about enemy eye level
if ( NPC->enemy )
{
// Find the height difference
dif = (NPC->enemy->currentOrigin[2]+NPC->enemy->maxs[2]) - NPC->currentOrigin[2];
// cap to prevent dramatic height shifts
if ( fabs( dif ) > 8 )
{
if ( fabs( dif ) > SENTRY_HOVER_HEIGHT )
{
dif = ( dif < 0 ? -24 : 24 );
}
NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
}
}
else
{
gentity_t *goal = NULL;
if ( NPCInfo->goalEntity ) // Is there a goal?
{
goal = NPCInfo->goalEntity;
}
else
{
goal = NPCInfo->lastGoalEntity;
}
if (goal)
{
dif = goal->currentOrigin[2] - NPC->currentOrigin[2];
if ( fabs( dif ) > SENTRY_HOVER_HEIGHT )
{
ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
}
else
{
if ( NPC->client->ps.velocity[2] )
{
NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY;
if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
{
NPC->client->ps.velocity[2] = 0;
}
}
}
}
// Apply friction to Z
else if ( NPC->client->ps.velocity[2] )
{
NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY;
if ( fabs( NPC->client->ps.velocity[2] ) < 1 )
{
NPC->client->ps.velocity[2] = 0;
}
}
}
// Apply friction
if ( NPC->client->ps.velocity[0] )
{
NPC->client->ps.velocity[0] *= SENTRY_VELOCITY_DECAY;
if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
{
NPC->client->ps.velocity[0] = 0;
}
}
if ( NPC->client->ps.velocity[1] )
{
NPC->client->ps.velocity[1] *= SENTRY_VELOCITY_DECAY;
if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
{
NPC->client->ps.velocity[1] = 0;
}
}
NPC_FaceEnemy( qtrue );
}
/*
-------------------------
Sentry_Idle
-------------------------
*/
void Sentry_Idle( void )
{
Sentry_MaintainHeight();
// Is he waking up?
if (NPCInfo->localState == LSTATE_WAKEUP)
{
if (NPC->client->ps.torsoAnimTimer<=0)
{
NPCInfo->scriptFlags |= SCF_LOOK_FOR_ENEMIES;
NPCInfo->burstCount = 0;
}
}
else
{
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SLEEP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
NPC->flags |= FL_SHIELDED;
NPC_BSIdle();
}
}
/*
-------------------------
Sentry_Strafe
-------------------------
*/
void Sentry_Strafe( void )
{
int dir;
vec3_t end, right;
trace_t tr;
AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
// Pick a random strafe direction, then check to see if doing a strafe would be
// reasonable valid
dir = ( rand() & 1 ) ? -1 : 1;
VectorMA( NPC->currentOrigin, SENTRY_STRAFE_DIS * dir, right, end );
gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
// Close enough
if ( tr.fraction > 0.9f )
{
VectorMA( NPC->client->ps.velocity, SENTRY_STRAFE_VEL * dir, right, NPC->client->ps.velocity );
// Add a slight upward push
NPC->client->ps.velocity[2] += SENTRY_UPWARD_PUSH;
// Set the strafe start time so we can do a controlled roll
NPC->fx_time = level.time;
NPCInfo->standTime = level.time + 3000 + random() * 500;
}
}
/*
-------------------------
Sentry_Hunt
-------------------------
*/
void Sentry_Hunt( qboolean visible, qboolean advance )
{
float distance, speed;
vec3_t forward;
//If we're not supposed to stand still, pursue the player
if ( NPCInfo->standTime < level.time )
{
// Only strafe when we can see the player
if ( visible )
{
Sentry_Strafe();
return;
}
}
//If we don't want to advance, stop here
if ( !advance && visible )
return;
//Only try and navigate if the player is visible
if ( visible == qfalse )
{
// Move towards our goal
NPCInfo->goalEntity = NPC->enemy;
NPCInfo->goalRadius = 12;
//Get our direction from the navigator if we can't see our target
if ( NPC_GetMoveDirection( forward, &distance ) == qfalse )
return;
}
else
{
VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward );
distance = VectorNormalize( forward );
}
speed = SENTRY_FORWARD_BASE_SPEED + SENTRY_FORWARD_MULTIPLIER * g_spskill->integer;
VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
}
/*
-------------------------
Sentry_RangedAttack
-------------------------
*/
void Sentry_RangedAttack( qboolean visible, qboolean advance )
{
if ( TIMER_Done( NPC, "attackDelay" ) && NPC->attackDebounceTime < level.time && visible ) // Attack?
{
if ( NPCInfo->burstCount > 6 )
{
NPCInfo->localState = LSTATE_ACTIVE;
NPCInfo->burstCount = 0;
TIMER_Set( NPC, "attackDelay", Q_irand( 2000, 3500) );
NPC->flags |= FL_SHIELDED;
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
G_Sound( NPC, G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_close.mp3" ));
}
else
{
Sentry_Fire();
}
}
if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
{
Sentry_Hunt( visible, advance );
}
}
/*
-------------------------
Sentry_AttackDecision
-------------------------
*/
void Sentry_AttackDecision( void )
{
// Always keep a good height off the ground
Sentry_MaintainHeight();
NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp.wav" );
//randomly talk
if ( TIMER_Done(NPC,"patrolNoise") )
{
if (TIMER_Done(NPC,"angerNoise"))
{
G_Sound( NPC, G_SoundIndex(va("sound/chars/sentry/misc/talk%d.wav", Q_irand(1, 3))));
TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) );
}
}
// He's dead.
if (NPC->enemy->health<1)
{
NPC->enemy = NULL;
Sentry_Idle();
return;
}
// If we don't have an enemy, just idle
if ( NPC_CheckEnemyExt() == qfalse )
{
Sentry_Idle();
return;
}
// Rate our distance to the target and visibilty
float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
qboolean visible = NPC_ClearLOS( NPC->enemy );
qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR);
// If we cannot see our target, move to see it
if ( visible == qfalse )
{
if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
{
Sentry_Hunt( visible, advance );
return;
}
}
NPC_FaceEnemy( qtrue );
Sentry_RangedAttack( visible, advance );
}
qboolean NPC_CheckPlayerTeamStealth( void );
/*
-------------------------
NPC_Sentry_Patrol
-------------------------
*/
void NPC_Sentry_Patrol( void )
{
Sentry_MaintainHeight();
//If we have somewhere to go, then do that
if (!NPC->enemy)
{
if ( NPC_CheckPlayerTeamStealth() )
{
//NPC_AngerSound();
NPC_UpdateAngles( qtrue, qtrue );
return;
}
if ( UpdateGoal() )
{
//start loop sound once we move
ucmd.buttons |= BUTTON_WALKING;
NPC_MoveToGoal( qtrue );
}
//randomly talk
if (TIMER_Done(NPC,"patrolNoise"))
{
G_Sound( NPC, G_SoundIndex(va("sound/chars/sentry/misc/talk%d.wav", Q_irand(1, 3))));
TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
}
}
NPC_UpdateAngles( qtrue, qtrue );
}
/*
-------------------------
NPC_BSSentry_Default
-------------------------
*/
void NPC_BSSentry_Default( void )
{
if ( NPC->targetname )
{
NPC->e_UseFunc = useF_sentry_use;
}
if (( NPC->enemy ) && (NPCInfo->localState != LSTATE_WAKEUP))
{
// Don't attack if waking up or if no enemy
Sentry_AttackDecision();
}
else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
{
NPC_Sentry_Patrol();
}
else
{
Sentry_Idle();
}
}