stvoy-sp-sdk/game/AI_Etherian.cpp

455 lines
9.5 KiB
C++

//Etherians
#include "AI.h"
#include "b_local.h"
#include "g_nav.h"
#include "anims.h"
extern void FX_StasisCharge( vec3_t origin, vec3_t dir, float perc_done );
extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time );
static void Etherian_Melee( void );
#define IDLE_RANGE 128
#define IDLE_RANGE_SQR ( IDLE_RANGE * IDLE_RANGE )
/*
-------------------------
Etherian_Stand
-------------------------
*/
static void Etherian_Stand( void )
{
PM_SetLegsAnimTimer( NPC, &NPC->client->ps.legsAnimTimer, 0 );
NPC_FaceEnemy();
NPC_UpdateAngles( qtrue, qtrue );
}
/*
-------------------------
Etherian_ClearLOS
-------------------------
*/
#define ETHERIAN_MELEE_OFS 28
static qboolean Etherian_ClearLOS( gentity_t *attacker, gentity_t *ent )
{
trace_t tr;
if ( ( attacker == NULL ) || ( ent == NULL ) )
return qfalse;
vec3_t start, end;
VectorCopy( attacker->currentOrigin, start );
start[2] += ETHERIAN_MELEE_OFS;
VectorCopy( ent->currentOrigin, end );
//end[2] += 8;
gi.trace ( &tr, start, NULL, NULL, end, attacker->s.number, CONTENTS_SOLID|CONTENTS_BODY );
if ( &g_entities[tr.entityNum] == ent )
return qtrue;
return qfalse;
}
/*
-------------------------
NPC_BSEtherian_Idle
-------------------------
*/
void NPC_BSEtherian_Idle( void )
{
//Idle here
NPC_BSIdle();
}
/*
-------------------------
Etherian_Hunt
-------------------------
*/
static void Etherian_Hunt( qboolean doMelee )
{
if ( NPC->enemy == NULL )
{
assert( 0 );
return;
}
if ( ( Etherian_ClearLOS( NPC, NPC->enemy ) == qfalse ) || ( Distance( NPC->currentOrigin, NPC->enemy->currentOrigin ) > 64 ) )
{
PM_SetLegsAnimTimer( NPC, &NPC->client->ps.legsAnimTimer, 0 );
//If we're not supposed to stand still, pursue the player
if ( NPCInfo->standTime < level.time )
{
//Move towards our goal
NPCInfo->combatMove = qtrue;
NPCInfo->goalEntity = NPC->enemy;
NPCInfo->goalRadius = 12;
NPC_MoveToGoal();
}
NPC_FaceEnemy();
NPC_UpdateAngles( qtrue, qtrue );
return;
}
//See if we want melee
if ( doMelee == qfalse )
return;
//Update our angles regardless
if ( NPC_FaceEnemy( qfalse ) )
{
NPC_UpdateAngles( qtrue, qtrue );
if ( NPCInfo->weaponTime < level.time )
{
NPCInfo->weaponTime = level.time + 400;
Etherian_Melee();
}
//Keep swinging
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
PM_SetLegsAnimTimer( NPC, &NPC->client->ps.legsAnimTimer, -1 );
}
}
/*
-------------------------
Etherian_Charge
-------------------------
*/
#define CHARGE_FORWARD 14.0f
#define CHARGE_UP 24.0f
static void Etherian_Charge( void )
{
vec3_t org, forward, up = { 0.0f, 0.0f, 1.0f };
NPC_FaceEnemy();
NPC_UpdateAngles( qtrue, qtrue );
// Move out in front of the alien, try to position roughly where hands will meet
AngleVectors( NPC->currentAngles, forward, NULL, NULL );
VectorMA( NPC->currentOrigin, CHARGE_FORWARD, forward, org );
VectorMA( org, CHARGE_UP, up, org );
FX_StasisCharge( org, forward, ( 0.75f + ( random() * 0.25f ) ) );
}
/*
-------------------------
Etherian_Melee
-------------------------
*/
#define ETHERIAN_MELEE_DAMAGE 4
#define ETHERIAN_MELEE_RANGE 64
#define ETHERIAN_MELEE_RANGE_SQR ( ETHERIAN_MELEE_RANGE * ETHERIAN_MELEE_RANGE )
static void Etherian_Melee( void )
{
if ( NPC->enemy == NULL )
return;
//Must first be within a valid range
if ( DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ) <= ETHERIAN_MELEE_RANGE_SQR )
{
//FIXME: Could be redundant
if ( Etherian_ClearLOS( NPC, NPC->enemy ) )
{
vec3_t dir;
VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, dir );
VectorNormalize( dir );
G_Damage( NPC->enemy, NPC, NPC, dir, NPC->enemy->currentOrigin, ETHERIAN_MELEE_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_STASIS );
Etherian_Charge();
}
}
}
/*
-------------------------
Etherian_AttemptFire
-------------------------
*/
static qboolean Etherian_AttemptFire( void )
{
if ( NPCInfo->weaponTime > level.time )
return qfalse;
int attackChance;
int attackLimit = 10;
switch ( g_spskill->integer )
{
case 0:
attackChance = 9;
break;
case 1:
attackChance = 7;
break;
default:
case 2:
attackChance = 4;
break;
}
return ( Q_irand( 0, attackLimit ) > attackChance );
}
/*
-------------------------
Etherian_Strafe
-------------------------
*/
void Etherian_Strafe( void )
{
vec3_t testpos, forward, right;
int dist = 64;
trace_t trace;
//Test left for clear movement and LOS
AngleVectors( NPC->currentAngles, forward, right, NULL );
if ( Q_irand( 0, 1 ) )
dist *= -1;
VectorMA( NPC->currentOrigin, dist, right, testpos );
NPCInfo->squadState = SQUAD_TRANSITION;
//Check that move
if ( NAV_CheckAhead( NPC, testpos, trace, NPC->clipmask ) == qfalse )
{
if ( trace.fraction > 0.5f )
{
NPC_SetMoveGoal( NPC, trace.endpos, 8, qtrue );
return;
}
else
{
VectorMA( right, -dist, right, testpos );
if ( NAV_CheckAhead( NPC, testpos, trace, NPC->clipmask ) == qfalse )
{
if ( trace.fraction > 0.5f )
{
NPC_SetMoveGoal( NPC, trace.endpos, 8, qtrue );
return;
}
}
NPC_SetMoveGoal( NPC, testpos, 8, qtrue );
return;
}
}
NPC_SetMoveGoal( NPC, testpos, 8, qtrue );
}
/*
-------------------------
Etherian_CheckMoveState
-------------------------
*/
qboolean Etherian_CheckMoveState( void )
{
//If we're moving, continue to do so
if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
{
//Did we make it?
if ( NAV_HitNavGoal( NPC->currentOrigin, NPC->mins, NPC->maxs, NPCInfo->goalEntity->currentOrigin, 16 ) )
{
TIMER_Set( NPC, "attackDelay", Q_irand( 750, 1250 ) ); //FIXME: Slant for difficulty levels
TIMER_Set( NPC, "strafe", Q_irand( 1000, 2000 ) );
NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
NPCInfo->goalEntity = NULL;
return qfalse;
}
//Keep running
NPCInfo->squadState = SQUAD_TRANSITION;
NPC_SlideMoveToGoal();
NPC_FaceEnemy( qtrue );
NPC_UpdateAngles( qtrue, qtrue );
return qtrue;
}
return qfalse;
}
/*
-------------------------
Etherian_Ranged
-------------------------
*/
#define MIN_MISSLE_DIST 300
#define MIN_MISSLE_DIST_SQR ( MIN_MISSLE_DIST * MIN_MISSLE_DIST )
static void Etherian_Ranged( void )
{
//If we're not ready to move again, just stay put
if ( NPCInfo->standTime > level.time )
{
NPC_FaceEnemy( qtrue );
NPC_UpdateAngles( qtrue, qtrue );
return;
}
//If we can't see the target, move to do so
if ( NPC_ClearShot( NPC->enemy ) == qfalse )
{
Etherian_Hunt( qfalse );
return;
}
//See if we should close up the gap a little
if ( DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ) > MIN_MISSLE_DIST_SQR )
{
Etherian_Hunt( qfalse );
return;
}
//See if we should fire at the player
if ( Etherian_AttemptFire() )
{
ucmd.buttons |= BUTTON_ATTACK;
NPC_ApplyWeaponFireDelay();
NPCInfo->standTime = level.time + 1000;
NPCInfo->weaponTime = level.time + Q_irand( 1000, 3000 );
}
NPC_FaceEnemy( qtrue );
NPC_UpdateAngles( qtrue, qtrue );
}
/*
-------------------------
Etherian_GetAttackerThreshold
-------------------------
*/
#define MAX_SWARM_EASY 1
#define MAX_SWARM_NORMAL 3
#define MAX_SWARM_HARD 5
static inline int Etherian_GetAttackerThreshold( void )
{
if ( g_spskill->integer == 0 )
return MAX_SWARM_EASY;
if ( g_spskill->integer == 1 )
return MAX_SWARM_NORMAL;
return MAX_SWARM_HARD;
}
/*
-------------------------
Etherian_DistributeAttack
-------------------------
*/
static qboolean Etherian_DistributeAttack( void )
{
int threshold = Etherian_GetAttackerThreshold();
gentity_t *enemy = AI_DistributeAttack( NPC, NPC->enemy, NPC->client->playerTeam, threshold );
//If we have no new target, then we're waiting
if ( enemy == NULL )
return qfalse;
//If the enemy is new, take it
if ( enemy != NPC->enemy )
{
G_SetEnemy( NPC, enemy );
return qtrue;
}
return qtrue;
}
/*
-------------------------
NPC_BSEtherian_Attack
-------------------------
*/
#define MIN_MELEE_RANGE 256
#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
void NPC_BSEtherian_Attack( void )
{
//Don't do anything if we're hurt
if ( NPC->painDebounceTime > level.time )
{
NPC_UpdateAngles( qtrue, qtrue );
return;
}
//See if we were going anywhere
if ( Etherian_CheckMoveState() )
return;
//If we don't have an enemy, just idle
if ( NPC_CheckEnemyExt() == qfalse )
{
NPC_BSEtherian_Idle();
return;
}
//Distribute our attack across the enemy team
if ( Etherian_DistributeAttack() == qfalse )
{
NPC_FaceEnemy( qtrue );
NPC_UpdateAngles( qtrue, qtrue );
return;
}
//Different behavior for projectile lobbers
if ( NPC->client->ps.weapon == WP_STASIS_ATTACK )
{
Etherian_Ranged();
return;
}
//Otherwise check to see how many group members are in front of you
AIGroupInfo_t group;
AI_GetGroup( group, NPC, 512 );
//If 2 or more, then continue to wait
if ( group.numFront >= 2 )
{
if ( DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ) > IDLE_RANGE_SQR )
{
Etherian_Hunt( qtrue );
return;
}
Etherian_Stand();
return;
}
//Otherwise charge in and attack
Etherian_Hunt( qtrue );
}