466 lines
10 KiB
C++
466 lines
10 KiB
C++
//The Vorhsoth
|
|
|
|
#include "AI.h"
|
|
#include "anims.h"
|
|
#include "b_local.h"
|
|
#include "g_functions.h"
|
|
|
|
extern void FX_GroundTendrilSpawner( vec3_t start, vec3_t end );
|
|
extern void FX_ChestBeamSpawner( vec3_t origin, float height );
|
|
extern void FX_Vohrsoth_CreateGroundWarning( vec3_t origin );
|
|
extern void CG_DrawEdge( vec3_t start, vec3_t end, int type );
|
|
extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time );
|
|
extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
|
|
|
|
//Attack state enums
|
|
enum
|
|
{
|
|
VLS_QUAD_LOBBER = 1,
|
|
VLS_TENDRIL,
|
|
VLS_ROCKET_BURST,
|
|
VLS_CHEST_BEAM,
|
|
};
|
|
|
|
//Attack times
|
|
const int QUAD_LOBBER_TIME = 4100;
|
|
const int CHEST_BEAM_TIME = 8000;
|
|
const int TENDRIL_TIME = 8100;
|
|
const int ROCKET_BURST_TIME = 4100;
|
|
|
|
/*
|
|
-------------------------
|
|
Vohrsoth_GetEnemyPosition
|
|
-------------------------
|
|
*/
|
|
|
|
static void Vohrsoth_GetEnemyPosition( vec3_t origin )
|
|
{
|
|
vec3_t end;
|
|
|
|
VectorCopy( NPC->enemy->currentOrigin, end );
|
|
|
|
end[2] -= 100;
|
|
|
|
trace_t tr;
|
|
|
|
gi.trace( &tr, NPC->enemy->currentOrigin, NULL, NULL, end, NPC->enemy->s.number, MASK_SOLID );
|
|
|
|
VectorCopy( tr.endpos, origin );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
tendril_think
|
|
-------------------------
|
|
*/
|
|
|
|
void tendril_think( gentity_t *self )
|
|
{
|
|
//Explode with effect
|
|
ExplodeDeath( self );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Vohrsoth_CreateTendril
|
|
-------------------------
|
|
*/
|
|
|
|
#define TENDRIL_DELAY 1000
|
|
|
|
static void Vohrsoth_CreateTendril( vec3_t origin )
|
|
{
|
|
vec3_t pos;
|
|
|
|
//Create an effect to mark the position
|
|
Vohrsoth_GetEnemyPosition( pos );
|
|
FX_Vohrsoth_CreateGroundWarning( pos );
|
|
|
|
//Spawn the entity
|
|
gentity_t *ent = G_Spawn();
|
|
|
|
if ( ent == NULL )
|
|
{
|
|
gi.Printf("WARNING: Unable to spawn ground tendril\n");
|
|
return;
|
|
}
|
|
|
|
//Make it invisible
|
|
ent->s.solid = 0;
|
|
ent->contents = 0;
|
|
ent->clipmask = 0;
|
|
ent->svFlags |= SVF_NOCLIENT;
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
ent->count = 0;
|
|
ent->classname = "tendril";
|
|
ent->splashDamage = 100;
|
|
ent->splashRadius = 64;
|
|
ent->spawnflags |= 4;
|
|
|
|
G_SetOrigin( ent, pos );
|
|
VectorSet( ent->s.angles, 0, 90, 0 );
|
|
|
|
ent->sounds = G_SoundIndex( va( "sound/weapons/explosions/explode%d.wav", Q_irand( 1, 4 ) ) );
|
|
|
|
ent->e_ThinkFunc = thinkF_ExplodeDeath;
|
|
ent->nextthink = level.time + TENDRIL_DELAY;
|
|
|
|
G_SoundOnEnt( ent, CHAN_WEAPON, "sound/enemies/vorhsoth/beamhit.wav" );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Vohrsoth_Tendril
|
|
-------------------------
|
|
*/
|
|
|
|
static void Vohrsoth_Tendril( void )
|
|
{
|
|
//If our enemy is dead, don't do anything
|
|
if ( NPC->enemy && NPC->enemy->health <= 0 )
|
|
return;
|
|
|
|
if ( TIMER_Done( NPC, "tendrilDelay" ) == qfalse )
|
|
return;
|
|
|
|
Vohrsoth_CreateTendril( NPC->enemy->currentOrigin );
|
|
|
|
TIMER_Set( NPC, "tendrilDelay", 1000 );
|
|
|
|
if ( TIMER_Done( NPC, "tendrilDrawDelay" ) == qfalse )
|
|
return;
|
|
|
|
vec3_t start, end, forward;
|
|
|
|
AngleVectors( NPC->currentAngles, forward, NULL, NULL );
|
|
|
|
VectorMA( NPC->currentOrigin, 80, forward, start );
|
|
|
|
VectorCopy( start, end );
|
|
|
|
start[2] -= 24;
|
|
end[2] += 48;
|
|
|
|
FX_GroundTendrilSpawner( start, end );
|
|
G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/enemies/vorhsoth/beamfire.wav" );
|
|
|
|
TIMER_Set( NPC, "tendrilDrawDelay", 99999999 );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Vohrsoth_CreateLob
|
|
-------------------------
|
|
*/
|
|
|
|
static void Vohrsoth_CreateLob( vec3_t start, vec3_t end )
|
|
{
|
|
vec3_t dir;
|
|
|
|
//Setup the direction
|
|
VectorSubtract( end, start, dir );
|
|
VectorNormalize( dir );
|
|
|
|
gentity_t *bolt;
|
|
|
|
bolt = G_Spawn();
|
|
|
|
if ( bolt == NULL )
|
|
return;
|
|
|
|
bolt->classname = "vorh_lob";
|
|
|
|
bolt->nextthink = level.time + 10000;
|
|
bolt->e_ThinkFunc = thinkF_G_FreeEntity;
|
|
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_FORGE_PROJ; //FIXME: For now
|
|
bolt->owner = NPC;
|
|
bolt->damage = 8 * 12;
|
|
bolt->dflags = 0;
|
|
bolt->splashDamage = 0;
|
|
bolt->splashRadius = 0;
|
|
bolt->methodOfDeath = MOD_SCAVENGER;
|
|
bolt->clipmask = MASK_SHOT;
|
|
|
|
// There are going to be a couple of different sized projectiles, so store 'em here
|
|
bolt->count = 1;
|
|
|
|
VectorSet( bolt->maxs, 2.0f, 2.0f, 2.0f );
|
|
VectorScale( bolt->maxs, -8, bolt->mins );
|
|
VectorScale( bolt->maxs, 8, bolt->maxs );
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time;
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
|
|
VectorScale( dir, 700, bolt->s.pos.trDelta );
|
|
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy (start, bolt->currentOrigin);
|
|
// Used by trails
|
|
VectorCopy (start, bolt->pos1 );
|
|
VectorCopy (start, bolt->pos2 );
|
|
|
|
G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/weapons/explosions/fireball.wav" );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Vohrsoth_QuadLobber
|
|
-------------------------
|
|
*/
|
|
|
|
const vec3_t lobAttackOffset[4] =
|
|
{
|
|
{ 200, -90, 48 },
|
|
{ 160, 70, 35 },
|
|
{ 120, 40, 80 },
|
|
{ 160, -75, 90 },
|
|
};
|
|
|
|
const int lobAttackDelay[4] =
|
|
{
|
|
700,
|
|
400,
|
|
600,
|
|
500
|
|
};
|
|
|
|
static void Vohrsoth_QuadLobber( void )
|
|
{
|
|
//If our enemy is dead, don't do anything
|
|
if ( NPC->enemy && NPC->enemy->health <= 0 )
|
|
return;
|
|
|
|
//See if we're ready to lob another
|
|
if ( TIMER_Done( NPC, "lobDelay" ) == qfalse )
|
|
return;
|
|
|
|
if ( NPCInfo->squadState > 3 )
|
|
return;
|
|
|
|
vec3_t start, forward, right;
|
|
|
|
AngleVectors( NPC->currentAngles, forward, right, NULL );
|
|
|
|
VectorMA( NPC->currentOrigin, lobAttackOffset[NPCInfo->squadState][0], forward, start );
|
|
VectorMA( start, lobAttackOffset[NPCInfo->squadState][1], right, start );
|
|
start[2] += lobAttackOffset[NPCInfo->squadState][2];
|
|
|
|
Vohrsoth_CreateLob( start, NPC->enemy->currentOrigin );
|
|
|
|
NPCInfo->squadState++;
|
|
TIMER_Set( NPC, "lobDelay", lobAttackDelay[NPCInfo->squadState] );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Vohrsoth_ChestBeam
|
|
-------------------------
|
|
*/
|
|
|
|
static void Vohrsoth_ChestBeam( void )
|
|
{
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Vohrsoth_Rocket
|
|
-------------------------
|
|
*/
|
|
|
|
static void Vohrsoth_Rocket( vec3_t start, vec3_t forward )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
bolt = G_Spawn();
|
|
|
|
VectorSet( bolt->mins, -4, -4, -4 );
|
|
VectorSet( bolt->maxs, 4, 4, 4 );
|
|
|
|
bolt->classname = "bot_rocket";
|
|
bolt->nextthink = level.time + 200;
|
|
bolt->e_ThinkFunc = thinkF_bot_rocket_think;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_BOT_ROCKET;
|
|
bolt->owner = NPC;
|
|
bolt->damage = 12;
|
|
bolt->dflags = 0;
|
|
bolt->splashDamage = 8;
|
|
bolt->splashRadius = 64;
|
|
bolt->methodOfDeath = MOD_BOTROCKET;
|
|
bolt->splashMethodOfDeath = MOD_BOTROCKET_SPLASH;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->contents = CONTENTS_SOLID;
|
|
bolt->takedamage = qtrue;
|
|
bolt->health = 10;
|
|
bolt->e_DieFunc = dieF_bot_rocket_die;
|
|
|
|
//Mark that this shouldn't account for FOV
|
|
bolt->spawnflags |= 2;
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( forward, 400 + ( crandom() * 100 ), bolt->s.pos.trDelta );
|
|
VectorCopy( forward, bolt->movedir );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( start, bolt->currentOrigin );
|
|
VectorCopy( start, bolt->pos1 );
|
|
|
|
gi.linkentity( bolt );
|
|
|
|
G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/weapons/hunter_seeker/fire.wav" );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Vohrsoth_RocketBurst
|
|
-------------------------
|
|
*/
|
|
|
|
const vec3_t rocketAttackOffset[4] =
|
|
{
|
|
{ 75, 0, 80 },
|
|
{ 90, 0, 85 },
|
|
{ 110, 0, 75 },
|
|
{ 120, 0, 70 },
|
|
};
|
|
|
|
static void Vohrsoth_RocketBurst( void )
|
|
{
|
|
//If our enemy is dead, don't do anything
|
|
if ( NPC->enemy && NPC->enemy->health <= 0 )
|
|
return;
|
|
|
|
if ( TIMER_Done( NPC, "rocketDelay" ) == qfalse )
|
|
return;
|
|
|
|
vec3_t start, forward, right, dir;
|
|
|
|
AngleVectors( NPC->currentAngles, forward, right, NULL );
|
|
|
|
for ( int i = 0; i < 4; i++ )
|
|
{
|
|
VectorMA( NPC->currentOrigin, rocketAttackOffset[i][0], forward, start );
|
|
VectorMA( start, rocketAttackOffset[i][1], right, start );
|
|
start[2] += rocketAttackOffset[i][2];
|
|
|
|
VectorSubtract( start, NPC->currentOrigin, dir );
|
|
VectorNormalize( dir );
|
|
|
|
Vohrsoth_Rocket( start, dir );
|
|
}
|
|
|
|
TIMER_Set( NPC, "rocketDelay", 500000 );
|
|
}
|
|
|
|
|
|
/*
|
|
-------------------------
|
|
Vohrsoth_CheckEnemy
|
|
-------------------------
|
|
*/
|
|
|
|
static void Vohrsoth_CheckEnemy( void )
|
|
{
|
|
gentity_t *target = &g_entities[0]; //FIXME: If you want something else, put it here
|
|
|
|
NPC->enemy = target;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Vohrsoth_StartAttack
|
|
-------------------------
|
|
*/
|
|
|
|
static void Vohrsoth_StartAttack( int attack )
|
|
{
|
|
int animID;
|
|
int timeOfs;
|
|
|
|
switch ( attack )
|
|
{
|
|
case VLS_QUAD_LOBBER:
|
|
animID = BOTH_ATTACK1;
|
|
timeOfs = QUAD_LOBBER_TIME;
|
|
NPCInfo->squadState = 0;
|
|
TIMER_Set( NPC, "lobDelay", lobAttackDelay[0] );
|
|
break;
|
|
|
|
case VLS_CHEST_BEAM:
|
|
animID = BOTH_ATTACK2;
|
|
timeOfs = CHEST_BEAM_TIME;
|
|
break;
|
|
|
|
case VLS_TENDRIL:
|
|
animID = BOTH_ATTACK4;
|
|
timeOfs = TENDRIL_TIME;
|
|
TIMER_Set( NPC, "tendrilDelay", 2500 );
|
|
TIMER_Set( NPC, "tendrilDrawDelay", 2500 );
|
|
break;
|
|
|
|
case VLS_ROCKET_BURST:
|
|
default:
|
|
animID = BOTH_ATTACK3;
|
|
timeOfs = ROCKET_BURST_TIME;
|
|
TIMER_Set( NPC, "rocketDelay", 800 );
|
|
break;
|
|
}
|
|
|
|
//Setup the information
|
|
NPC_SetAnim( NPC, SETANIM_BOTH, animID, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
|
|
TIMER_Set( NPC, "attackDelay", timeOfs );
|
|
NPCInfo->localState = attack;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_BSVohrsoth_Attack
|
|
-------------------------
|
|
*/
|
|
|
|
void NPC_BSVohrsoth_Attack( void )
|
|
{
|
|
//If we're dead, stop what we're doing
|
|
if ( NPC->health <= 0 )
|
|
return;
|
|
|
|
//Check for enemies, or loss of them
|
|
Vohrsoth_CheckEnemy();
|
|
|
|
//If our enemy is dead, don't do anything
|
|
if ( NPC->enemy && NPC->enemy->health <= 0 )
|
|
return;
|
|
|
|
//See if we're ready to attack again
|
|
if ( TIMER_Done( NPC, "attackDelay" ) )
|
|
{
|
|
Vohrsoth_StartAttack( Q_irand( VLS_QUAD_LOBBER, VLS_ROCKET_BURST ) );
|
|
}
|
|
|
|
//Decide which attack state we're in and update anything we need to
|
|
switch ( NPCInfo->localState )
|
|
{
|
|
case VLS_QUAD_LOBBER:
|
|
Vohrsoth_QuadLobber();
|
|
break;
|
|
|
|
case VLS_CHEST_BEAM:
|
|
Vohrsoth_ChestBeam();
|
|
break;
|
|
|
|
case VLS_TENDRIL:
|
|
Vohrsoth_Tendril();
|
|
break;
|
|
|
|
case VLS_ROCKET_BURST:
|
|
Vohrsoth_RocketBurst();
|
|
break;
|
|
}
|
|
}
|