You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
858 lines
22 KiB
858 lines
22 KiB
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
|
|
// events
|
|
//
|
|
// $Workfile: $
|
|
// $Date: $
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// $Log: $
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "ai_default.h"
|
|
#include "ai_task.h"
|
|
#include "ai_schedule.h"
|
|
#include "ai_node.h"
|
|
#include "ai_hull.h"
|
|
#include "ai_hint.h"
|
|
#include "ai_route.h"
|
|
#include "soundent.h"
|
|
#include "game.h"
|
|
#include "npcevent.h"
|
|
#include "entitylist.h"
|
|
#include "activitylist.h"
|
|
#include "animation.h"
|
|
#include "basecombatweapon.h"
|
|
#include "IEffects.h"
|
|
#include "vstdlib/random.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "ammodef.h"
|
|
#include "hl1_ai_basenpc.h"
|
|
|
|
#define AFLOCK_MAX_RECRUIT_RADIUS 1024
|
|
#define AFLOCK_FLY_SPEED 125
|
|
#define AFLOCK_TURN_RATE 75
|
|
#define AFLOCK_ACCELERATE 10
|
|
#define AFLOCK_CHECK_DIST 192
|
|
#define AFLOCK_TOO_CLOSE 100
|
|
#define AFLOCK_TOO_FAR 256
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
class CNPC_FlockingFlyerFlock : public CHL1BaseNPC
|
|
{
|
|
DECLARE_CLASS( CNPC_FlockingFlyerFlock, CHL1BaseNPC );
|
|
public:
|
|
|
|
void Spawn( void );
|
|
void Precache( void );
|
|
bool KeyValue( const char *szKeyName, const char *szValue );
|
|
void SpawnFlock( void );
|
|
|
|
// Sounds are shared by the flock
|
|
static void PrecacheFlockSounds( void );
|
|
|
|
DECLARE_DATADESC();
|
|
|
|
int m_cFlockSize;
|
|
float m_flFlockRadius;
|
|
};
|
|
|
|
BEGIN_DATADESC( CNPC_FlockingFlyerFlock )
|
|
DEFINE_FIELD( m_cFlockSize, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flFlockRadius, FIELD_FLOAT ),
|
|
END_DATADESC()
|
|
|
|
class CNPC_FlockingFlyer : public CHL1BaseNPC
|
|
{
|
|
DECLARE_CLASS( CNPC_FlockingFlyer, CHL1BaseNPC );
|
|
public:
|
|
void Spawn( void );
|
|
void Precache( void );
|
|
void SpawnCommonCode( void );
|
|
void IdleThink( void );
|
|
void BoidAdvanceFrame( void );
|
|
void Start( void );
|
|
bool FPathBlocked( void );
|
|
void FlockLeaderThink( void );
|
|
void SpreadFlock( void );
|
|
void SpreadFlock2( void );
|
|
void MakeSound( void );
|
|
void FlockFollowerThink( void );
|
|
void Event_Killed( const CTakeDamageInfo &info );
|
|
void FallHack( void );
|
|
//void Poop ( void ); Adrian - wtf?!
|
|
|
|
|
|
|
|
int IsLeader( void ) { return m_pSquadLeader == this; }
|
|
int InSquad( void ) { return m_pSquadLeader != NULL; }
|
|
int SquadCount( void );
|
|
void SquadRemove( CNPC_FlockingFlyer *pRemove );
|
|
void SquadUnlink( void );
|
|
void SquadAdd( CNPC_FlockingFlyer *pAdd );
|
|
void SquadDisband( void );
|
|
|
|
CNPC_FlockingFlyer *m_pSquadLeader;
|
|
CNPC_FlockingFlyer *m_pSquadNext;
|
|
bool m_fTurning;// is this boid turning?
|
|
bool m_fCourseAdjust;// followers set this flag TRUE to override flocking while they avoid something
|
|
bool m_fPathBlocked;// TRUE if there is an obstacle ahead
|
|
Vector m_vecReferencePoint;// last place we saw leader
|
|
Vector m_vecAdjustedVelocity;// adjusted velocity (used when fCourseAdjust is TRUE)
|
|
float m_flGoalSpeed;
|
|
float m_flLastBlockedTime;
|
|
float m_flFakeBlockedTime;
|
|
float m_flAlertTime;
|
|
float m_flFlockNextSoundTime;
|
|
float m_flTempVar;
|
|
|
|
DECLARE_DATADESC();
|
|
};
|
|
|
|
BEGIN_DATADESC( CNPC_FlockingFlyer )
|
|
DEFINE_FIELD( m_pSquadLeader, FIELD_CLASSPTR ),
|
|
DEFINE_FIELD( m_pSquadNext, FIELD_CLASSPTR ),
|
|
DEFINE_FIELD( m_fTurning, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_fCourseAdjust, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_fPathBlocked, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_vecReferencePoint, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_vecAdjustedVelocity, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_flGoalSpeed, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flLastBlockedTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flFakeBlockedTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flAlertTime, FIELD_TIME ),
|
|
DEFINE_THINKFUNC( IdleThink ),
|
|
DEFINE_THINKFUNC( Start ),
|
|
DEFINE_THINKFUNC( FlockLeaderThink ),
|
|
DEFINE_THINKFUNC( FlockFollowerThink ),
|
|
DEFINE_THINKFUNC( FallHack ),
|
|
|
|
DEFINE_FIELD( m_flFlockNextSoundTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flTempVar, FIELD_FLOAT ),
|
|
|
|
END_DATADESC()
|
|
|
|
LINK_ENTITY_TO_CLASS( monster_flyer, CNPC_FlockingFlyer );
|
|
LINK_ENTITY_TO_CLASS( monster_flyer_flock, CNPC_FlockingFlyerFlock );
|
|
|
|
bool CNPC_FlockingFlyerFlock::KeyValue( const char *szKeyName, const char *szValue )
|
|
{
|
|
if ( FStrEq( szKeyName, "iFlockSize" ) )
|
|
{
|
|
m_cFlockSize = atoi( szValue );
|
|
return true;
|
|
}
|
|
else if ( FStrEq( szKeyName, "flFlockRadius" ) )
|
|
{
|
|
m_flFlockRadius = atof( szValue );
|
|
return true;
|
|
}
|
|
else
|
|
BaseClass::KeyValue( szKeyName, szValue );
|
|
|
|
return false;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CNPC_FlockingFlyerFlock::Spawn( void )
|
|
{
|
|
Precache( );
|
|
|
|
SetRenderColor( 255, 255, 255, 255 );
|
|
SpawnFlock();
|
|
|
|
|
|
SetThink( &CBaseEntity::SUB_Remove );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CNPC_FlockingFlyerFlock::Precache( void )
|
|
{
|
|
//PRECACHE_MODEL("models/aflock.mdl");
|
|
PrecacheModel("models/boid.mdl");
|
|
|
|
PrecacheFlockSounds();
|
|
}
|
|
|
|
void CNPC_FlockingFlyerFlock::SpawnFlock( void )
|
|
{
|
|
float R = m_flFlockRadius;
|
|
int iCount;
|
|
Vector vecSpot;
|
|
CNPC_FlockingFlyer *pBoid, *pLeader;
|
|
|
|
pLeader = pBoid = NULL;
|
|
|
|
for ( iCount = 0 ; iCount < m_cFlockSize ; iCount++ )
|
|
{
|
|
pBoid = CREATE_ENTITY( CNPC_FlockingFlyer, "monster_flyer" );
|
|
|
|
if ( !pLeader )
|
|
{
|
|
// make this guy the leader.
|
|
pLeader = pBoid;
|
|
|
|
pLeader->m_pSquadLeader = pLeader;
|
|
pLeader->m_pSquadNext = NULL;
|
|
}
|
|
|
|
vecSpot.x = random->RandomFloat( -R, R );
|
|
vecSpot.y = random->RandomFloat( -R, R );
|
|
vecSpot.z = random->RandomFloat( 0, 16 );
|
|
vecSpot = GetAbsOrigin() + vecSpot;
|
|
|
|
UTIL_SetOrigin( pBoid, vecSpot);
|
|
pBoid->SetMoveType( MOVETYPE_FLY );
|
|
pBoid->SpawnCommonCode();
|
|
pBoid->SetGroundEntity( NULL );
|
|
pBoid->SetAbsVelocity( Vector ( 0, 0, 0 ) );
|
|
pBoid->SetAbsAngles( GetAbsAngles() );
|
|
|
|
pBoid->SetCycle( 0 );
|
|
pBoid->SetThink( &CNPC_FlockingFlyer::IdleThink );
|
|
pBoid->SetNextThink( gpGlobals->curtime + 0.2 );
|
|
|
|
if ( pBoid != pLeader )
|
|
{
|
|
pLeader->SquadAdd( pBoid );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CNPC_FlockingFlyerFlock::PrecacheFlockSounds( void )
|
|
{
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::Spawn( )
|
|
{
|
|
Precache( );
|
|
SpawnCommonCode();
|
|
|
|
SetCycle( 0 );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
SetThink( &CNPC_FlockingFlyer::IdleThink );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::SpawnCommonCode( )
|
|
{
|
|
m_lifeState = LIFE_ALIVE;
|
|
SetClassname( "monster_flyer" );
|
|
SetSolid( SOLID_BBOX );
|
|
AddSolidFlags( FSOLID_NOT_STANDABLE );
|
|
SetMoveType( MOVETYPE_FLY );
|
|
m_takedamage = DAMAGE_NO;
|
|
m_iHealth = 1;
|
|
|
|
m_fPathBlocked = FALSE;// obstacles will be detected
|
|
m_flFieldOfView = 0.2;
|
|
m_flTempVar = 0;
|
|
|
|
//SET_MODEL(ENT(pev), "models/aflock.mdl");
|
|
SetModel( "models/boid.mdl" );
|
|
|
|
// UTIL_SetSize(this, Vector(0,0,0), Vector(0,0,0));
|
|
UTIL_SetSize(this, Vector(-5,-5,0), Vector(5,5,2));
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::Precache( )
|
|
{
|
|
//PRECACHE_MODEL("models/aflock.mdl");
|
|
PrecacheModel("models/boid.mdl");
|
|
CNPC_FlockingFlyerFlock::PrecacheFlockSounds();
|
|
|
|
PrecacheScriptSound( "FlockingFlyer.Alert" );
|
|
PrecacheScriptSound( "FlockingFlyer.Idle" );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::IdleThink( void )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + 0.2 );
|
|
|
|
// see if there's a client in the same pvs as the monster
|
|
if ( !FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
|
|
{
|
|
SetThink( &CNPC_FlockingFlyer::Start );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//
|
|
// SquadUnlink(), Unlink the squad pointers.
|
|
//
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::SquadUnlink( void )
|
|
{
|
|
m_pSquadLeader = NULL;
|
|
m_pSquadNext = NULL;
|
|
}
|
|
|
|
//=========================================================
|
|
//
|
|
// SquadAdd(), add pAdd to my squad
|
|
//
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::SquadAdd( CNPC_FlockingFlyer *pAdd )
|
|
{
|
|
ASSERT( pAdd!=NULL );
|
|
ASSERT( !pAdd->InSquad() );
|
|
ASSERT( this->IsLeader() );
|
|
|
|
pAdd->m_pSquadNext = m_pSquadNext;
|
|
m_pSquadNext = pAdd;
|
|
pAdd->m_pSquadLeader = this;
|
|
}
|
|
//=========================================================
|
|
//
|
|
// SquadRemove(), remove pRemove from my squad.
|
|
// If I am pRemove, promote m_pSquadNext to leader
|
|
//
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::SquadRemove( CNPC_FlockingFlyer *pRemove )
|
|
{
|
|
ASSERT( pRemove!=NULL );
|
|
ASSERT( this->IsLeader() );
|
|
ASSERT( pRemove->m_pSquadLeader == this );
|
|
|
|
if ( SquadCount() > 2 )
|
|
{
|
|
// Removing the leader, promote m_pSquadNext to leader
|
|
if ( pRemove == this )
|
|
{
|
|
CNPC_FlockingFlyer *pLeader = m_pSquadNext;
|
|
|
|
// copy the enemy LKP to the new leader
|
|
|
|
// if ( GetEnemy() )
|
|
// pLeader->m_vecEnemyLKP = m_vecEnemyLKP;
|
|
|
|
if ( pLeader )
|
|
{
|
|
CNPC_FlockingFlyer *pList = pLeader;
|
|
|
|
while ( pList )
|
|
{
|
|
pList->m_pSquadLeader = pLeader;
|
|
pList = pList->m_pSquadNext;
|
|
}
|
|
|
|
}
|
|
SquadUnlink();
|
|
}
|
|
else // removing a node
|
|
{
|
|
CNPC_FlockingFlyer *pList = this;
|
|
|
|
// Find the node before pRemove
|
|
while ( pList->m_pSquadNext != pRemove )
|
|
{
|
|
// assert to test valid list construction
|
|
ASSERT( pList->m_pSquadNext != NULL );
|
|
pList = pList->m_pSquadNext;
|
|
}
|
|
// List validity
|
|
ASSERT( pList->m_pSquadNext == pRemove );
|
|
|
|
// Relink without pRemove
|
|
pList->m_pSquadNext = pRemove->m_pSquadNext;
|
|
|
|
// Unlink pRemove
|
|
pRemove->SquadUnlink();
|
|
}
|
|
}
|
|
else
|
|
SquadDisband();
|
|
}
|
|
//=========================================================
|
|
//
|
|
// SquadCount(), return the number of members of this squad
|
|
// callable from leaders & followers
|
|
//
|
|
//=========================================================
|
|
int CNPC_FlockingFlyer::SquadCount( void )
|
|
{
|
|
CNPC_FlockingFlyer *pList = m_pSquadLeader;
|
|
int squadCount = 0;
|
|
while ( pList )
|
|
{
|
|
squadCount++;
|
|
pList = pList->m_pSquadNext;
|
|
}
|
|
|
|
return squadCount;
|
|
}
|
|
|
|
//=========================================================
|
|
//
|
|
// SquadDisband(), Unlink all squad members
|
|
//
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::SquadDisband( void )
|
|
{
|
|
CNPC_FlockingFlyer *pList = m_pSquadLeader;
|
|
CNPC_FlockingFlyer *pNext;
|
|
|
|
while ( pList )
|
|
{
|
|
pNext = pList->m_pSquadNext;
|
|
pList->SquadUnlink();
|
|
pList = pNext;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// Start - player enters the pvs, so get things going.
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::Start( void )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
if ( IsLeader() )
|
|
{
|
|
SetThink( &CNPC_FlockingFlyer::FlockLeaderThink );
|
|
}
|
|
else
|
|
{
|
|
SetThink( &CNPC_FlockingFlyer::FlockFollowerThink );
|
|
}
|
|
|
|
SetActivity ( ACT_FLY );
|
|
ResetSequenceInfo( );
|
|
BoidAdvanceFrame( );
|
|
|
|
m_flSpeed = AFLOCK_FLY_SPEED;// no delay!
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::BoidAdvanceFrame ( void )
|
|
{
|
|
float flapspeed = ( m_flSpeed - m_flTempVar ) / AFLOCK_ACCELERATE;
|
|
m_flTempVar = m_flTempVar * .8 + m_flSpeed * .2;
|
|
|
|
if (flapspeed < 0) flapspeed = -flapspeed;
|
|
if (flapspeed < 0.25) flapspeed = 0.25;
|
|
if (flapspeed > 1.9) flapspeed = 1.9;
|
|
|
|
m_flPlaybackRate = flapspeed;
|
|
|
|
QAngle angVel = GetLocalAngularVelocity();
|
|
|
|
// lean
|
|
angVel.x = - GetAbsAngles().x + flapspeed * 5;
|
|
|
|
// bank
|
|
angVel.z = - GetAbsAngles().z + angVel.y;
|
|
|
|
SetLocalAngularVelocity( angVel );
|
|
|
|
// pev->framerate = flapspeed;
|
|
StudioFrameAdvance();
|
|
}
|
|
|
|
//=========================================================
|
|
// Leader boids use this think every tenth
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::FlockLeaderThink( void )
|
|
{
|
|
trace_t tr;
|
|
Vector vecDist;// used for general measurements
|
|
Vector vecDir;// used for general measurements
|
|
float flLeftSide;
|
|
float flRightSide;
|
|
Vector vForward, vRight, vUp;
|
|
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
AngleVectors ( GetAbsAngles(), &vForward, &vRight, &vUp );
|
|
|
|
// is the way ahead clear?
|
|
if ( !FPathBlocked () )
|
|
{
|
|
// if the boid is turning, stop the trend.
|
|
if ( m_fTurning )
|
|
{
|
|
m_fTurning = FALSE;
|
|
|
|
QAngle angVel = GetLocalAngularVelocity();
|
|
angVel.y = 0;
|
|
SetLocalAngularVelocity( angVel );
|
|
}
|
|
|
|
m_fPathBlocked = FALSE;
|
|
|
|
if ( m_flSpeed <= AFLOCK_FLY_SPEED )
|
|
m_flSpeed += 5;
|
|
|
|
SetAbsVelocity( vForward * m_flSpeed );
|
|
|
|
BoidAdvanceFrame( );
|
|
|
|
return;
|
|
}
|
|
|
|
// IF we get this far in the function, the leader's path is blocked!
|
|
m_fPathBlocked = TRUE;
|
|
|
|
if ( !m_fTurning)// something in the way and boid is not already turning to avoid
|
|
{
|
|
// measure clearance on left and right to pick the best dir to turn
|
|
UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vRight * AFLOCK_CHECK_DIST, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
|
|
vecDist = (tr.endpos - GetAbsOrigin());
|
|
flRightSide = vecDist.Length();
|
|
|
|
UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() - vRight * AFLOCK_CHECK_DIST, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
|
|
vecDist = (tr.endpos - GetAbsOrigin());
|
|
flLeftSide = vecDist.Length();
|
|
|
|
// turn right if more clearance on right side
|
|
if ( flRightSide > flLeftSide )
|
|
{
|
|
QAngle angVel = GetLocalAngularVelocity();
|
|
angVel.y = -AFLOCK_TURN_RATE;
|
|
SetLocalAngularVelocity( angVel );
|
|
|
|
m_fTurning = TRUE;
|
|
}
|
|
// default to left turn :)
|
|
else if ( flLeftSide > flRightSide )
|
|
{
|
|
QAngle angVel = GetLocalAngularVelocity();
|
|
angVel.y = AFLOCK_TURN_RATE;
|
|
SetLocalAngularVelocity( angVel );
|
|
|
|
m_fTurning = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// equidistant. Pick randomly between left and right.
|
|
m_fTurning = TRUE;
|
|
|
|
QAngle angVel = GetLocalAngularVelocity();
|
|
|
|
if ( random->RandomInt( 0, 1 ) == 0 )
|
|
{
|
|
angVel.y = AFLOCK_TURN_RATE;
|
|
}
|
|
else
|
|
{
|
|
angVel.y = -AFLOCK_TURN_RATE;
|
|
}
|
|
|
|
SetLocalAngularVelocity( angVel );
|
|
}
|
|
}
|
|
|
|
SpreadFlock( );
|
|
|
|
SetAbsVelocity( vForward * m_flSpeed );
|
|
|
|
// check and make sure we aren't about to plow into the ground, don't let it happen
|
|
UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() - vUp * 16, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
|
|
if (tr.fraction != 1.0 && GetAbsVelocity().z < 0 )
|
|
{
|
|
Vector vecVel = GetAbsVelocity();
|
|
vecVel.z = 0;
|
|
SetAbsVelocity( vecVel );
|
|
}
|
|
|
|
// maybe it did, though.
|
|
if ( GetFlags() & FL_ONGROUND )
|
|
{
|
|
UTIL_SetOrigin( this, GetAbsOrigin() + Vector ( 0 , 0 , 1 ) );
|
|
Vector vecVel = GetAbsVelocity();
|
|
vecVel.z = 0;
|
|
SetAbsVelocity( vecVel );
|
|
}
|
|
|
|
if ( m_flFlockNextSoundTime < gpGlobals->curtime )
|
|
{
|
|
// MakeSound();
|
|
m_flFlockNextSoundTime = gpGlobals->curtime + random->RandomFloat( 1, 3 );
|
|
}
|
|
|
|
BoidAdvanceFrame( );
|
|
|
|
return;
|
|
}
|
|
|
|
//=========================================================
|
|
// FBoidPathBlocked - returns TRUE if there is an obstacle ahead
|
|
//=========================================================
|
|
bool CNPC_FlockingFlyer::FPathBlocked( void )
|
|
{
|
|
trace_t tr;
|
|
Vector vecDist;// used for general measurements
|
|
Vector vecDir;// used for general measurements
|
|
bool fBlocked;
|
|
Vector vForward, vRight, vUp;
|
|
|
|
if ( m_flFakeBlockedTime > gpGlobals->curtime )
|
|
{
|
|
m_flLastBlockedTime = gpGlobals->curtime;
|
|
return TRUE;
|
|
}
|
|
|
|
// use VELOCITY, not angles, not all boids point the direction they are flying
|
|
//vecDir = UTIL_VecToAngles( pevBoid->velocity );
|
|
AngleVectors ( GetAbsAngles(), &vForward, &vRight, &vUp );
|
|
|
|
fBlocked = FALSE;// assume the way ahead is clear
|
|
|
|
// check for obstacle ahead
|
|
UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vForward * AFLOCK_CHECK_DIST, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
|
|
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
m_flLastBlockedTime = gpGlobals->curtime;
|
|
fBlocked = TRUE;
|
|
}
|
|
|
|
// extra wide checks
|
|
UTIL_TraceLine(GetAbsOrigin() + vRight * 12, GetAbsOrigin() + vRight * 12 + vForward * AFLOCK_CHECK_DIST, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
|
|
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
m_flLastBlockedTime = gpGlobals->curtime;
|
|
fBlocked = TRUE;
|
|
}
|
|
|
|
UTIL_TraceLine(GetAbsOrigin() - vRight * 12, GetAbsOrigin() - vRight * 12 + vForward * AFLOCK_CHECK_DIST, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
|
|
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
m_flLastBlockedTime = gpGlobals->curtime;
|
|
fBlocked = TRUE;
|
|
}
|
|
|
|
if ( !fBlocked && gpGlobals->curtime - m_flLastBlockedTime > 6 )
|
|
{
|
|
// not blocked, and it's been a few seconds since we've actually been blocked.
|
|
m_flFakeBlockedTime = gpGlobals->curtime + random->RandomInt(1, 3);
|
|
}
|
|
|
|
return fBlocked;
|
|
}
|
|
|
|
//=========================================================
|
|
// Searches for boids that are too close and pushes them away
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::SpreadFlock( )
|
|
{
|
|
Vector vecDir;
|
|
float flSpeed;// holds vector magnitude while we fiddle with the direction
|
|
|
|
CNPC_FlockingFlyer *pList = m_pSquadLeader;
|
|
while ( pList )
|
|
{
|
|
if ( pList != this && ( GetAbsOrigin() - pList->GetAbsOrigin() ).Length() <= AFLOCK_TOO_CLOSE )
|
|
{
|
|
// push the other away
|
|
vecDir = ( pList->GetAbsOrigin() - GetAbsOrigin() );
|
|
VectorNormalize( vecDir );
|
|
|
|
// store the magnitude of the other boid's velocity, and normalize it so we
|
|
// can average in a course that points away from the leader.
|
|
flSpeed = pList->GetAbsVelocity().Length();
|
|
|
|
Vector vecVel = pList->GetAbsVelocity();
|
|
VectorNormalize( vecVel );
|
|
pList->SetAbsVelocity( ( vecVel + vecDir ) * 0.5 * flSpeed );
|
|
}
|
|
|
|
pList = pList->m_pSquadNext;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// Alters the caller's course if he's too close to others
|
|
//
|
|
// This function should **ONLY** be called when Caller's velocity is normalized!!
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::SpreadFlock2 ( )
|
|
{
|
|
Vector vecDir;
|
|
|
|
CNPC_FlockingFlyer *pList = m_pSquadLeader;
|
|
|
|
while ( pList )
|
|
{
|
|
if ( pList != this && ( GetAbsOrigin() - pList->GetAbsOrigin() ).Length() <= AFLOCK_TOO_CLOSE )
|
|
{
|
|
vecDir = ( GetAbsOrigin() - pList->GetAbsOrigin() );
|
|
VectorNormalize( vecDir );
|
|
|
|
SetAbsVelocity( ( GetAbsVelocity() + vecDir ) );
|
|
}
|
|
|
|
pList = pList->m_pSquadNext;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::MakeSound( void )
|
|
{
|
|
if ( m_flAlertTime > gpGlobals->curtime )
|
|
{
|
|
CPASAttenuationFilter filter1( this );
|
|
|
|
// make agitated sounds
|
|
EmitSound( filter1, entindex(), "FlockingFlyer.Alert" );
|
|
return;
|
|
}
|
|
|
|
// make normal sound
|
|
CPASAttenuationFilter filter2( this );
|
|
|
|
EmitSound( filter2, entindex(), "FlockingFlyer.Idle" );
|
|
}
|
|
|
|
//=========================================================
|
|
// follower boids execute this code when flocking
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::FlockFollowerThink( void )
|
|
{
|
|
Vector vecDist;
|
|
Vector vecDir;
|
|
Vector vecDirToLeader;
|
|
float flDistToLeader;
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
if ( IsLeader() || !InSquad() )
|
|
{
|
|
// the leader has been killed and this flyer suddenly finds himself the leader.
|
|
SetThink ( &CNPC_FlockingFlyer::FlockLeaderThink );
|
|
return;
|
|
}
|
|
|
|
vecDirToLeader = ( m_pSquadLeader->GetAbsOrigin() - GetAbsOrigin() );
|
|
flDistToLeader = vecDirToLeader.Length();
|
|
|
|
// match heading with leader
|
|
SetAbsAngles( m_pSquadLeader->GetAbsAngles() );
|
|
|
|
//
|
|
// We can see the leader, so try to catch up to it
|
|
//
|
|
if ( FInViewCone ( m_pSquadLeader ) )
|
|
{
|
|
// if we're too far away, speed up
|
|
if ( flDistToLeader > AFLOCK_TOO_FAR )
|
|
{
|
|
m_flGoalSpeed = m_pSquadLeader->GetAbsVelocity().Length() * 1.5;
|
|
}
|
|
|
|
// if we're too close, slow down
|
|
else if ( flDistToLeader < AFLOCK_TOO_CLOSE )
|
|
{
|
|
m_flGoalSpeed = m_pSquadLeader->GetAbsVelocity().Length() * 0.5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// wait up! the leader isn't out in front, so we slow down to let him pass
|
|
m_flGoalSpeed = m_pSquadLeader->GetAbsVelocity().Length() * 0.5;
|
|
}
|
|
|
|
SpreadFlock2();
|
|
|
|
Vector vecVel = GetAbsVelocity();
|
|
m_flSpeed = vecVel.Length();
|
|
VectorNormalize( vecVel );
|
|
|
|
// if we are too far from leader, average a vector towards it into our current velocity
|
|
if ( flDistToLeader > AFLOCK_TOO_FAR )
|
|
{
|
|
VectorNormalize( vecDirToLeader );
|
|
vecVel = (vecVel + vecDirToLeader) * 0.5;
|
|
}
|
|
|
|
// clamp speeds and handle acceleration
|
|
if ( m_flGoalSpeed > AFLOCK_FLY_SPEED * 2 )
|
|
{
|
|
m_flGoalSpeed = AFLOCK_FLY_SPEED * 2;
|
|
}
|
|
|
|
if ( m_flSpeed < m_flGoalSpeed )
|
|
{
|
|
m_flSpeed += AFLOCK_ACCELERATE;
|
|
}
|
|
else if ( m_flSpeed > m_flGoalSpeed )
|
|
{
|
|
m_flSpeed -= AFLOCK_ACCELERATE;
|
|
}
|
|
|
|
SetAbsVelocity( vecVel * m_flSpeed );
|
|
|
|
BoidAdvanceFrame( );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CNPC_FlockingFlyer::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
CNPC_FlockingFlyer *pSquad;
|
|
|
|
pSquad = (CNPC_FlockingFlyer *)m_pSquadLeader;
|
|
|
|
while ( pSquad )
|
|
{
|
|
pSquad->m_flAlertTime = gpGlobals->curtime + 15;
|
|
pSquad = (CNPC_FlockingFlyer *)pSquad->m_pSquadNext;
|
|
}
|
|
|
|
if ( m_pSquadLeader )
|
|
{
|
|
m_pSquadLeader->SquadRemove( this );
|
|
}
|
|
|
|
m_lifeState = LIFE_DEAD;
|
|
|
|
m_flPlaybackRate = 0;
|
|
IncrementInterpolationFrame();
|
|
|
|
UTIL_SetSize( this, Vector(0,0,0), Vector(0,0,0) );
|
|
SetMoveType( MOVETYPE_FLYGRAVITY );
|
|
|
|
SetThink ( &CNPC_FlockingFlyer::FallHack );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
}
|
|
|
|
void CNPC_FlockingFlyer::FallHack( void )
|
|
{
|
|
if ( GetFlags() & FL_ONGROUND )
|
|
{
|
|
CBaseEntity *groundentity = GetContainingEntity( GetGroundEntity()->edict() );
|
|
|
|
if ( !FClassnameIs ( groundentity, "worldspawn" ) )
|
|
{
|
|
SetGroundEntity( NULL );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
}
|
|
else
|
|
{
|
|
SetAbsVelocity( Vector( 0, 0, 0 ) );
|
|
SetThink( NULL );
|
|
}
|
|
}
|
|
}
|