597 lines
14 KiB
C++
597 lines
14 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 );
|
|
|
|
//Local state enums
|
|
enum
|
|
{
|
|
LSTATE_NONE = 0,
|
|
LSTATE_BACKINGUP,
|
|
LSTATE_SPINNING,
|
|
LSTATE_PAIN,
|
|
LSTATE_DROP
|
|
};
|
|
|
|
void ImperialProbe_Idle( void );
|
|
|
|
void NPC_Probe_Precache(void)
|
|
{
|
|
for ( int i = 1; i < 4; i++)
|
|
{
|
|
G_SoundIndex( va( "sound/chars/probe/misc/probetalk%d", i ) );
|
|
}
|
|
G_SoundIndex( "sound/chars/probe/misc/probedroidloop" );
|
|
G_SoundIndex("sound/chars/probe/misc/anger1");
|
|
G_SoundIndex("sound/chars/probe/misc/fire");
|
|
|
|
G_EffectIndex( "chunks/probehead" );
|
|
G_EffectIndex( "env/med_explode2" );
|
|
G_EffectIndex( "explosions/probeexplosion1");
|
|
G_EffectIndex( "bryar/muzzle_flash" );
|
|
|
|
RegisterItem( FindItemForAmmo( AMMO_BLASTER ));
|
|
RegisterItem( FindItemForWeapon( WP_BRYAR_PISTOL ) );
|
|
}
|
|
/*
|
|
-------------------------
|
|
Hunter_MaintainHeight
|
|
-------------------------
|
|
*/
|
|
|
|
#define VELOCITY_DECAY 0.85f
|
|
|
|
void ImperialProbe_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];
|
|
|
|
// cap to prevent dramatic height shifts
|
|
if ( fabs( dif ) > 8 )
|
|
{
|
|
if ( fabs( dif ) > 16 )
|
|
{
|
|
dif = ( dif < 0 ? -16 : 16 );
|
|
}
|
|
|
|
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 ) > 24 )
|
|
{
|
|
ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
|
|
}
|
|
else
|
|
{
|
|
if ( NPC->client->ps.velocity[2] )
|
|
{
|
|
NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
|
|
|
|
if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
|
|
{
|
|
NPC->client->ps.velocity[2] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Apply friction
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
ImperialProbe_Strafe
|
|
-------------------------
|
|
*/
|
|
|
|
#define HUNTER_STRAFE_VEL 256
|
|
#define HUNTER_STRAFE_DIS 200
|
|
#define HUNTER_UPWARD_PUSH 32
|
|
|
|
void ImperialProbe_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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
ImperialProbe_Hunt
|
|
-------------------------`
|
|
*/
|
|
|
|
#define HUNTER_FORWARD_BASE_SPEED 10
|
|
#define HUNTER_FORWARD_MULTIPLIER 5
|
|
|
|
void ImperialProbe_Hunt( qboolean visible, qboolean advance )
|
|
{
|
|
float distance, speed;
|
|
vec3_t forward;
|
|
|
|
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
|
|
|
|
//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 )
|
|
{
|
|
ImperialProbe_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;
|
|
|
|
NPC_MoveToGoal(qtrue);
|
|
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 );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
ImperialProbe_FireBlaster
|
|
-------------------------
|
|
*/
|
|
void ImperialProbe_FireBlaster(void)
|
|
{
|
|
vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1;
|
|
static vec3_t forward, vright, up;
|
|
static vec3_t muzzle;
|
|
gentity_t *missile;
|
|
mdxaBone_t boltMatrix;
|
|
|
|
//FIXME: use {0, NPC->client->ps.legsYaw, 0}
|
|
gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel,
|
|
NPC->genericBolt1,
|
|
&boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
|
|
NULL, NPC->s.modelScale );
|
|
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 );
|
|
|
|
G_PlayEffect( "bryar/muzzle_flash", muzzle1 );
|
|
|
|
G_Sound( NPC, G_SoundIndex( "sound/chars/probe/misc/fire" ));
|
|
|
|
if (NPC->health)
|
|
{
|
|
CalcEntitySpot( NPC->enemy, SPOT_CHEST, enemy_org1 );
|
|
enemy_org1[0]+= Q_irand(0,10);
|
|
enemy_org1[1]+= Q_irand(0,10);
|
|
VectorSubtract (enemy_org1, muzzle1, delta1);
|
|
vectoangles ( delta1, angleToEnemy1 );
|
|
AngleVectors (angleToEnemy1, forward, vright, up);
|
|
}
|
|
else
|
|
{
|
|
AngleVectors (NPC->currentAngles, forward, vright, up);
|
|
}
|
|
|
|
missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC );
|
|
|
|
missile->classname = "bryar_proj";
|
|
missile->s.weapon = WP_BRYAR_PISTOL;
|
|
|
|
if ( g_spskill->integer <= 1 )
|
|
{
|
|
missile->damage = 5;
|
|
}
|
|
else
|
|
{
|
|
missile->damage = 10;
|
|
}
|
|
|
|
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
missile->methodOfDeath = MOD_ENERGY;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
ImperialProbe_Ranged
|
|
-------------------------
|
|
*/
|
|
void ImperialProbe_Ranged( qboolean visible, qboolean advance )
|
|
{
|
|
int delay_min,delay_max;
|
|
|
|
if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack?
|
|
{
|
|
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
delay_min = 500;
|
|
delay_max = 3000;
|
|
}
|
|
else if ( g_spskill->integer > 1 )
|
|
{
|
|
delay_min = 500;
|
|
delay_max = 2000;
|
|
}
|
|
else
|
|
{
|
|
delay_min = 300;
|
|
delay_max = 1500;
|
|
}
|
|
|
|
TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) );
|
|
ImperialProbe_FireBlaster();
|
|
// ucmd.buttons |= BUTTON_ATTACK;
|
|
}
|
|
|
|
if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
|
|
{
|
|
ImperialProbe_Hunt( visible, advance );
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
ImperialProbe_AttackDecision
|
|
-------------------------
|
|
*/
|
|
|
|
#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 ImperialProbe_AttackDecision( void )
|
|
{
|
|
// Always keep a good height off the ground
|
|
ImperialProbe_MaintainHeight();
|
|
|
|
//randomly talk
|
|
if ( TIMER_Done(NPC,"patrolNoise") )
|
|
{
|
|
if (TIMER_Done(NPC,"angerNoise"))
|
|
{
|
|
G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) );
|
|
|
|
TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) );
|
|
}
|
|
}
|
|
|
|
// If we don't have an enemy, just idle
|
|
if ( NPC_CheckEnemyExt() == qfalse )
|
|
{
|
|
ImperialProbe_Idle();
|
|
return;
|
|
}
|
|
|
|
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL);
|
|
|
|
// 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 )
|
|
{
|
|
if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
|
|
{
|
|
ImperialProbe_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
|
|
ImperialProbe_Ranged( visible, advance );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_BSDroid_Pain
|
|
-------------------------
|
|
*/
|
|
void NPC_Probe_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
|
|
{
|
|
float pain_chance;
|
|
|
|
VectorCopy( self->NPC->lastPathAngles, self->s.angles );
|
|
|
|
if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) // demp2 always messes them up real good
|
|
{
|
|
vec3_t endPos;
|
|
trace_t trace;
|
|
|
|
VectorSet( endPos, self->currentOrigin[0], self->currentOrigin[1], self->currentOrigin[2] - 128 );
|
|
gi.trace( &trace, self->currentOrigin, NULL, NULL, endPos, self->s.number, MASK_SOLID );
|
|
|
|
if ( trace.fraction == 1.0f || mod == MOD_DEMP2 ) // demp2 always does this
|
|
{
|
|
if (self->client->clientInfo.headModel != 0)
|
|
{
|
|
vec3_t origin;
|
|
|
|
VectorCopy(self->currentOrigin,origin);
|
|
origin[2] +=50;
|
|
// G_PlayEffect( "small_chunks", origin );
|
|
G_PlayEffect( "chunks/probehead", origin );
|
|
G_PlayEffect( "env/med_explode2", origin );
|
|
self->client->clientInfo.headModel = 0;
|
|
self->client->moveType = MT_RUNJUMP;
|
|
self->client->ps.gravity = g_gravity->value*.1;
|
|
}
|
|
|
|
if ( (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) && other )
|
|
{
|
|
vec3_t dir;
|
|
|
|
NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
|
|
|
|
VectorSubtract( self->currentOrigin, other->currentOrigin, dir );
|
|
VectorNormalize( dir );
|
|
|
|
VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity );
|
|
self->client->ps.velocity[2] -= 127;
|
|
}
|
|
|
|
self->s.powerups |= ( 1 << PW_SHOCKED );
|
|
self->client->ps.powerups[PW_SHOCKED] = level.time + 3000;
|
|
|
|
self->NPC->localState = LSTATE_DROP;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pain_chance = NPC_GetPainChance( self, damage );
|
|
|
|
if ( random() < pain_chance ) // Spin around in pain?
|
|
{
|
|
NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE);
|
|
}
|
|
}
|
|
|
|
NPC_Pain( self, inflictor, other, point, damage, mod);
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
ImperialProbe_Idle
|
|
-------------------------
|
|
*/
|
|
|
|
void ImperialProbe_Idle( void )
|
|
{
|
|
ImperialProbe_MaintainHeight();
|
|
|
|
NPC_BSIdle();
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_BSImperialProbe_Patrol
|
|
-------------------------
|
|
*/
|
|
void ImperialProbe_Patrol( void )
|
|
{
|
|
ImperialProbe_MaintainHeight();
|
|
|
|
if ( NPC_CheckPlayerTeamStealth() )
|
|
{
|
|
NPC_UpdateAngles( qtrue, qtrue );
|
|
return;
|
|
}
|
|
|
|
//If we have somewhere to go, then do that
|
|
if (!NPC->enemy)
|
|
{
|
|
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL );
|
|
|
|
if ( UpdateGoal() )
|
|
{
|
|
//start loop sound once we move
|
|
NPC->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" );
|
|
ucmd.buttons |= BUTTON_WALKING;
|
|
NPC_MoveToGoal( qtrue );
|
|
}
|
|
//randomly talk
|
|
if (TIMER_Done(NPC,"patrolNoise"))
|
|
{
|
|
G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) );
|
|
|
|
TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
|
|
}
|
|
}
|
|
else // He's got an enemy. Make him angry.
|
|
{
|
|
G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/probe/misc/anger1" );
|
|
TIMER_Set( NPC, "angerNoise", Q_irand( 2000, 4000 ) );
|
|
//NPCInfo->behaviorState = BS_HUNT_AND_KILL;
|
|
}
|
|
|
|
NPC_UpdateAngles( qtrue, qtrue );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
ImperialProbe_Wait
|
|
-------------------------
|
|
*/
|
|
void ImperialProbe_Wait(void)
|
|
{
|
|
if ( NPCInfo->localState == LSTATE_DROP )
|
|
{
|
|
vec3_t endPos;
|
|
trace_t trace;
|
|
|
|
NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->desiredYaw + 25 );
|
|
|
|
VectorSet( endPos, NPC->currentOrigin[0], NPC->currentOrigin[1], NPC->currentOrigin[2] - 32 );
|
|
gi.trace( &trace, NPC->currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID );
|
|
|
|
if ( trace.fraction != 1.0f )
|
|
{
|
|
G_Damage(NPC, NPC->enemy, NPC->enemy, NULL, NULL, 2000, 0,MOD_UNKNOWN);
|
|
}
|
|
}
|
|
|
|
NPC_UpdateAngles( qtrue, qtrue );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_BSImperialProbe_Default
|
|
-------------------------
|
|
*/
|
|
void NPC_BSImperialProbe_Default( void )
|
|
{
|
|
|
|
if ( NPC->enemy )
|
|
{
|
|
NPCInfo->goalEntity = NPC->enemy;
|
|
ImperialProbe_AttackDecision();
|
|
}
|
|
else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
|
|
{
|
|
ImperialProbe_Patrol();
|
|
}
|
|
else if ( NPCInfo->localState == LSTATE_DROP )
|
|
{
|
|
ImperialProbe_Wait();
|
|
}
|
|
else
|
|
{
|
|
ImperialProbe_Idle();
|
|
}
|
|
}
|