//========= 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 ); } } }