stvoy-sp-sdk/game/AI_HunterSeeker.cpp

343 lines
8.3 KiB
C++

#include "b_local.h"
#include "g_nav.h"
/*
-------------------------
Hunter_MaintainHeight
-------------------------
*/
#define VELOCITY_DECAY 0.85f
void Hunter_MaintainHeight( void )
{
float dif;
vec3_t endPos;
trace_t trace;
// 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->currentOrigin[2] + 46; // ??magic number
// cap to prevent dramatic height shifts
if ( fabs( dif ) > 16 )
{
dif = ( dif < 0 ? -16 : 16 );
}
ucmd.upmove = dif * 3;
}
else
{
// Stay at a given height until we take on an enemy
VectorSet( endPos, NPC->currentOrigin[0], NPC->currentOrigin[1], NPC->currentOrigin[2] - 512 );
gi.trace( &trace, NPC->currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID );
if ( trace.fraction != 1.0f )
{
float length = ( trace.fraction * 512 );
if ( length < 80 )
{
ucmd.upmove = 32;
}
else if ( length > 120 )
{
ucmd.upmove = -32;
}
else
{
if ( NPC->client->ps.velocity[2] )
{
NPC->client->ps.velocity[2] *= 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] *= 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] *= VELOCITY_DECAY;
if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
{
NPC->client->ps.velocity[1] = 0;
}
}
}
/*
-------------------------
HunterSeeker_Strafe
-------------------------
*/
#define HUNTER_STRAFE_VEL 256
#define HUNTER_STRAFE_DIS 200
#define HUNTER_UPWARD_PUSH 32
void HunterSeeker_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, HUNTER_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, HUNTER_STRAFE_VEL * dir, right, NPC->client->ps.velocity );
// Add a slight upward push
NPC->client->ps.velocity[2] += HUNTER_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;
}
}
/*
-------------------------
HunterSeeker_Hunt
-------------------------
*/
#define HUNTER_FORWARD_BASE_SPEED 10
#define HUNTER_FORWARD_MULTIPLIER 5
void HunterSeeker_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 )
{
HunterSeeker_Strafe();
return;
}
}
//If we don't want to advance, stop here
if ( advance == qfalse )
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 = HUNTER_FORWARD_BASE_SPEED + HUNTER_FORWARD_MULTIPLIER * g_spskill->integer;
VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
}
/*
-------------------------
HunterSeeker_Melee
-------------------------
*/
void HunterSeeker_Melee( qboolean visible, qboolean advance )
{
// Make sure that if we are switching weapon states that the burstCount gets cleared.
// Otherwise, it could easily be using the burstCount from the other weapon attack
if ( !NPC->trigger_formation )
{
NPCInfo->burstCount = 0;
NPC->trigger_formation = qtrue;
}
// Attempt to fire off a shot at the player
if ( NPCInfo->weaponTime < level.time )
{
// Burst count is based off of skill setting
NPCInfo->burstCount = Q_irand( 2, 4 ) * ( g_spskill->integer + 1);
NPCInfo->weaponTime = level.time + 5000; // Time to do any attack again
}
else if ( NPCInfo->burstCount && ( NPCInfo->pauseTime < level.time ) )
{
// Fire
ucmd.buttons |= BUTTON_ALT_ATTACK;
// Setup delays
NPCInfo->pauseTime = level.time + ( 3 - g_spskill->integer ) * 100; // Time to fire a ranged shot again
NPCInfo->burstCount--;
NPC->count = !NPC->count; // Alternate the muzzle
}
HunterSeeker_Hunt( visible, advance );
}
/*
-------------------------
HunterSeeker_Ranged
-------------------------
*/
void HunterSeeker_Ranged( qboolean visible, qboolean advance )
{
// Make sure that if we are switching weapon states that the burstCount gets cleared.
// Otherwise, it could easily be using the burstCount from the other weapon attack
if ( NPC->trigger_formation )
{
NPCInfo->burstCount = 0;
NPC->trigger_formation = qfalse;
}
// Attempt to fire off a burst of rockets at the player
if ( ( Q_irand( 0, 10 ) > 8 ) && ( NPCInfo->weaponTime < level.time ) )
{
// Burst count is based off of skill setting
NPCInfo->burstCount = (g_spskill->integer%3) + 1;
NPCInfo->weaponTime = level.time + 5000; // Time to do any attack again
}
else if ( NPCInfo->burstCount && ( NPCInfo->pauseTime < level.time ) )
{
// Fire
ucmd.buttons |= BUTTON_ATTACK;
// Setup delays
NPCInfo->pauseTime = level.time + 200 + random() * 100; // Time to fire the next rocket in the burst
NPCInfo->burstCount--;
}
HunterSeeker_Hunt( visible, advance );
}
/*
-------------------------
NPC_BSHunterSeeker_Attack
-------------------------
*/
#define MIN_MELEE_RANGE 320
#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
#define MIN_DISTANCE 128
#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
void NPC_BSHunterSeeker_Attack( void )
{
// Always keep a good height off the ground
Hunter_MaintainHeight();
// If we don't have an enemy, just idle
if ( NPC_CheckEnemyExt() == qfalse )
{
NPC_BSHunterSeeker_Idle();
return;
}
// Rate our distance to the target, and our visibilty
float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
distance_e distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE;
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 )
{
HunterSeeker_Hunt( visible, advance );
return;
}
// Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb
NPC_FaceEnemy( qtrue );
// Decide what type of attack to do
switch ( distRate )
{
case DIST_MELEE:
HunterSeeker_Melee( visible, advance );
break;
case DIST_LONG:
HunterSeeker_Ranged( visible, advance );
break;
}
}
/*
-------------------------
NPC_BSHunterSeeker_Idle
-------------------------
*/
void NPC_BSHunterSeeker_Idle( void )
{
Hunter_MaintainHeight();
NPC_BSIdle();
}
/*QUAKED NPC_HunterSeeker(1 0 0) (-10 -10 0) (10 10 20) x x x x DROPTOFLOOR SILENTSPAWN NOTSOLID STARTINSOLID SHY
*/
extern void SP_NPC_spawner( gentity_t *self );
void SP_NPC_HunterSeeker( gentity_t *self )
{
//FIXME: make an index into an external string table for localization
if (g_language && Q_stricmp("DEUTSCH",g_language->string)==0)
{
self->fullName = "Fremdartige Lebensform";
}
else
{
self->fullName = "Alien Lifeform";
}
if(!self->NPC_type)
self->NPC_type = "hunterseeker";
// For some reason, they don't always have their weapon precached unless you do this right away?
RegisterItem( FindItemForWeapon( WP_BOT_ROCKET ) );
SP_NPC_spawner( self );
}