343 lines
8.3 KiB
C++
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 );
|
||
|
}
|