mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-28 23:12:04 +00:00
577 lines
13 KiB
C
577 lines
13 KiB
C
#include "b_local.h"
|
|
#include "g_nav.h"
|
|
|
|
#include "../namespace_begin.h"
|
|
extern gitem_t *BG_FindItemForAmmo( ammo_t ammo );
|
|
#include "../namespace_end.h"
|
|
extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
|
|
|
|
#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)
|
|
{
|
|
int i;
|
|
|
|
G_SoundIndex( "sound/chars/sentry/misc/sentry_explo" );
|
|
G_SoundIndex( "sound/chars/sentry/misc/sentry_pain" );
|
|
G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_open" );
|
|
G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_close" );
|
|
G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" );
|
|
G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" );
|
|
|
|
for ( i = 1; i < 4; i++)
|
|
{
|
|
G_SoundIndex( va( "sound/chars/sentry/misc/talk%d", i ) );
|
|
}
|
|
|
|
G_EffectIndex( "bryar/muzzle_flash");
|
|
G_EffectIndex( "env/med_explode");
|
|
|
|
RegisterItem( BG_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 *attacker, int damage)
|
|
{
|
|
int mod = gPainMOD;
|
|
|
|
NPC_Pain( self, attacker, damage );
|
|
|
|
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( self, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_pain") );
|
|
|
|
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;
|
|
int which;
|
|
|
|
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, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_shield_open") );
|
|
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?
|
|
which = NPCInfo->burstCount % 3;
|
|
switch( which )
|
|
{
|
|
case 0:
|
|
bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash1");
|
|
break;
|
|
case 1:
|
|
bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash2");
|
|
break;
|
|
case 2:
|
|
default:
|
|
bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash03");
|
|
}
|
|
|
|
trap_G2API_GetBoltMatrix( NPC->ghoul2, 0,
|
|
bolt,
|
|
&boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time,
|
|
NULL, NPC->modelScale );
|
|
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle );
|
|
|
|
AngleVectors( NPC->r.currentAngles, forward, vright, up );
|
|
// G_Sound( NPC, G_SoundIndex("sound/chars/sentry/misc/shoot.wav"));
|
|
|
|
G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle, forward );
|
|
|
|
missile = CreateMissile( muzzle, forward, 1600, 10000, NPC, qfalse );
|
|
|
|
missile->classname = "bryar_proj";
|
|
missile->s.weapon = WP_BRYAR_PISTOL;
|
|
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
missile->methodOfDeath = MOD_BRYAR_PISTOL;
|
|
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" );
|
|
|
|
// 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->r.currentOrigin[2]+NPC->enemy->r.maxs[2]) - NPC->r.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->r.currentOrigin[2] - NPC->r.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.torsoTimer<=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->r.currentOrigin, SENTRY_STRAFE_DIS * dir, right, end );
|
|
|
|
trap_Trace( &tr, NPC->r.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->r.currentOrigin, NPC->r.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 )
|
|
{
|
|
if ( !NPC->fly_sound_debounce_time )
|
|
{//delay closing down to give the player an opening
|
|
NPC->fly_sound_debounce_time = level.time + Q_irand( 500, 2000 );
|
|
}
|
|
else if ( NPC->fly_sound_debounce_time < level.time )
|
|
{
|
|
NPCInfo->localState = LSTATE_ACTIVE;
|
|
NPC->fly_sound_debounce_time = 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_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/sentry/misc/sentry_shield_close" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Sentry_Fire();
|
|
}
|
|
}
|
|
|
|
if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
|
|
{
|
|
Sentry_Hunt( visible, advance );
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Sentry_AttackDecision
|
|
-------------------------
|
|
*/
|
|
void Sentry_AttackDecision( void )
|
|
{
|
|
float distance;
|
|
qboolean visible;
|
|
qboolean advance;
|
|
|
|
// Always keep a good height off the ground
|
|
Sentry_MaintainHeight();
|
|
|
|
NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" );
|
|
|
|
//randomly talk
|
|
if ( TIMER_Done(NPC,"patrolNoise") )
|
|
{
|
|
if (TIMER_Done(NPC,"angerNoise"))
|
|
{
|
|
G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", 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) == qfalse )
|
|
{
|
|
Sentry_Idle();
|
|
return;
|
|
}
|
|
|
|
// Rate our distance to the target and visibilty
|
|
distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );
|
|
visible = NPC_ClearLOS4( NPC->enemy );
|
|
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_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", 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->use = 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();
|
|
}
|
|
}
|