source-engine/game/client/swarm/c_asw_alien.cpp
2023-10-03 17:23:56 +03:00

694 lines
19 KiB
C++

#include "cbase.h"
#include "c_asw_alien.h"
#include "eventlist.h"
#include "decals.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "c_asw_generic_emitter_entity.h"
#include "c_asw_marine_resource.h"
#include "c_asw_marine.h"
#include "c_asw_game_resource.h"
#include "asw_shareddefs.h"
#include "tier0/vprof.h"
#include "c_asw_fx.h"
#include "datacache/imdlcache.h"
#include "baseparticleentity.h"
#include "c_asw_clientragdoll.h"
#include "asw_util_shared.h"
#include "functionproxy.h"
#include "imaterialproxydict.h"
#include "proxyentity.h"
#include "materialsystem/IMaterialVar.h"
#include "materialsystem/itexture.h"
//#include "c_asw_physics_prop_statue.h"
#include "c_asw_mesh_emitter_entity.h"
#include "c_asw_egg.h"
#include "props_shared.h"
#include "c_asw_player.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define ASW_GIB_ASAP 0.175f
ConVar asw_alien_object_motion_blur_scale( "asw_alien_object_motion_blur_scale", "0.2" );
ConVar asw_drone_gib_time_min("asw_drone_gib_time_min", "0.2", 0, "Minimum time a Swarm Drone ragdoll will stay around before gibbing");
ConVar asw_drone_gib_time_max("asw_drone_gib_time_max", "0.2", 0, "Maximum time a Swarm Drone ragdoll will stay around before gibbing");
ConVar asw_drone_fade_time_min("asw_drone_fade_time_min", "2.0", 0, "Minimum time a Swarm Drone ragdoll will stay around before fading");
ConVar asw_drone_fade_time_max("asw_drone_fade_time_max", "4.0", 0, "Maximum time a Swarm Drone ragdoll will stay around before fading");
ConVar asw_directional_shadows("asw_directional_shadows", "1", 0, "Whether aliens should have flashlight directional shadows");
ConVar asw_alien_shadows("asw_alien_shadows", "0", 0, "If set to one, aliens will always have shadows (WARNING: Big fps cost when lots of aliens are active)");
ConVar asw_alien_footstep_interval( "asw_alien_footstep_interval", "0.25", 0, "Minimum interval between alien footstep sounds. Used to keep them from piling up and preventing others from playing." );
ConVar asw_breakable_aliens( "asw_breakable_aliens", "1", 0, "If set, aliens can break into ragdoll gibs" );
extern ConVar asw_override_footstep_volume;
extern ConVar asw_alien_debug_death_style;
IMPLEMENT_NETWORKCLASS_ALIASED( ASW_Alien, DT_ASW_Alien )
BEGIN_NETWORK_TABLE( CASW_Alien, DT_ASW_Alien )
RecvPropVectorXY( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ), 0, C_BaseEntity::RecvProxy_CellOriginXY ),
RecvPropFloat( RECVINFO_NAME( m_vecNetworkOrigin[2], m_vecOrigin[2] ), 0, C_BaseEntity::RecvProxy_CellOriginZ ),
RecvPropFloat( RECVINFO_NAME( m_angNetworkAngles[0], m_angRotation[0] ) ),
RecvPropFloat( RECVINFO_NAME( m_angNetworkAngles[1], m_angRotation[1] ) ),
RecvPropFloat( RECVINFO_NAME( m_angNetworkAngles[2], m_angRotation[2] ) ),
RecvPropBool( RECVINFO( m_bElectroStunned ) ),// not using ElectroStunned
//RecvPropBool( RECVINFO( m_bElectroShockSmall ) ),
//RecvPropBool( RECVINFO( m_bElectroShockBig ) ),
RecvPropBool( RECVINFO( m_bOnFire ) ),
RecvPropInt( RECVINFO( m_nDeathStyle ), SPROP_UNSIGNED ),
RecvPropInt ( RECVINFO( m_iHealth) ),
END_RECV_TABLE()
PRECACHE_REGISTER_BEGIN( GLOBAL, ASW_Alien )
PRECACHE( MATERIAL, "effects/TiledFire/fire_tiled_precache" )
PRECACHE( MATERIAL, "effects/model_layer_shock_1_precache" )
PRECACHE( MATERIAL, "effects/model_layer_ice_1_precache" )
PRECACHE( PARTICLE_SYSTEM, "damage_numbers" )
PRECACHE_REGISTER_END()
IMPLEMENT_AUTO_LIST( IClientAimTargetsAutoList );
float C_ASW_Alien::sm_flLastFootstepTime = 0.0f;
C_ASW_Alien::C_ASW_Alien() :
m_GlowObject( this ),
m_MotionBlurObject( this, 0.0f )
{
m_bStepSideLeft = false;
m_nLastSetModel = 0;
m_fNextElectroStunEffect = 0;
m_fLastCustomContribution = 0;
m_vecLastCustomDir = vec3_origin;
m_iLastCustomFrame = -1;
m_bClientOnFire = false;
m_vecLastRenderedPos = vec3_origin;
m_pBurningEffect = NULL;
m_GlowObject.SetColor( Vector( 0.3f, 0.6f, 0.1f ) );
m_GlowObject.SetAlpha( 0.55f );
m_GlowObject.SetRenderFlags( false, false );
m_GlowObject.SetFullBloomRender( true );
}
C_ASW_Alien::~C_ASW_Alien()
{
m_bOnFire = false;
UpdateFireEmitters();
}
void C_ASW_Alien::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options )
{
if ( event == AE_ASW_FOOTSTEP || event == AE_MARINE_FOOTSTEP )
{
Vector vel;
EstimateAbsVelocity( vel );
surfacedata_t *pSurface = GetGroundSurface();
if (pSurface)
MarineStepSound( pSurface, GetAbsOrigin(), vel );
}
else if (event == AE_REMOVE_CLIENT_AIM)
{
IASW_Client_Aim_Target::Remove( this );
}
else if ( event == AE_RAGDOLL )
{
}
BaseClass::FireEvent(origin, angles, event, options);
}
void C_ASW_Alien::MarineStepSound( surfacedata_t *psurface, const Vector &vecOrigin, const Vector &vecVelocity )
{
int fWalking;
float fvol;
Vector knee;
Vector feet;
float height;
float speed;
float velrun;
float velwalk;
float flduck;
int fLadder;
if ( GetFlags() & (FL_FROZEN|FL_ATCONTROLS))
return;
if ( GetMoveType() == MOVETYPE_NOCLIP || GetMoveType() == MOVETYPE_OBSERVER )
return;
if ( gpGlobals->curtime - sm_flLastFootstepTime < asw_alien_footstep_interval.GetFloat() )
return;
speed = VectorLength( vecVelocity );
float groundspeed = Vector2DLength( vecVelocity.AsVector2D() );
// determine if we are on a ladder
fLadder = ( GetMoveType() == MOVETYPE_LADDER );
// UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!!
if ( ( GetFlags() & FL_DUCKING) || fLadder )
{
velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow
velrun = 80;
flduck = 100;
}
else
{
velwalk = 90;
velrun = 220;
flduck = 0;
}
bool onground = true; //( GetFlags() & FL_ONGROUND );
bool movingalongground = ( groundspeed > 0.0f );
bool moving_fast_enough = ( speed >= velwalk );
// To hear step sounds you must be either on a ladder or moving along the ground AND
// You must be moving fast enough
//Msg("og=%d ma=%d mf=%d\n", onground, movingalongground, moving_fast_enough);
if ( !moving_fast_enough || !(fLadder || ( onground && movingalongground )) )
return;
// MoveHelper()->PlayerSetAnimation( PLAYER_WALK );
fWalking = speed < velrun;
VectorCopy( vecOrigin, knee );
VectorCopy( vecOrigin, feet );
height = 72.0f; // bad
knee[2] = vecOrigin[2] + 0.2 * height;
// find out what we're stepping in or on...
if ( fLadder )
{
psurface = physprops->GetSurfaceData( physprops->GetSurfaceIndex( "ladder" ) );
fvol = 0.5;
}
else if ( enginetrace->GetPointContents( knee ) & MASK_WATER )
{
static int iSkipStep = 0;
if ( iSkipStep == 0 )
{
iSkipStep++;
return;
}
if ( iSkipStep++ == 3 )
{
iSkipStep = 0;
}
psurface = physprops->GetSurfaceData( physprops->GetSurfaceIndex( "wade" ) );
fvol = 0.65;
}
else if ( enginetrace->GetPointContents( feet ) & MASK_WATER )
{
psurface = physprops->GetSurfaceData( physprops->GetSurfaceIndex( "water" ) );
fvol = fWalking ? 0.2 : 0.5;
}
else
{
if ( !psurface )
return;
switch ( psurface->game.material )
{
default:
case CHAR_TEX_CONCRETE:
fvol = fWalking ? 0.2 : 0.5;
break;
case CHAR_TEX_METAL:
fvol = fWalking ? 0.2 : 0.5;
break;
case CHAR_TEX_DIRT:
fvol = fWalking ? 0.25 : 0.55;
break;
case CHAR_TEX_VENT:
fvol = fWalking ? 0.4 : 0.7;
break;
case CHAR_TEX_GRATE:
fvol = fWalking ? 0.2 : 0.5;
break;
case CHAR_TEX_TILE:
fvol = fWalking ? 0.2 : 0.5;
break;
case CHAR_TEX_SLOSH:
fvol = fWalking ? 0.2 : 0.5;
break;
}
}
// play the sound
// 65% volume if ducking
if ( GetFlags() & FL_DUCKING )
{
fvol *= 0.65;
}
if ( asw_override_footstep_volume.GetFloat() > 0 )
{
fvol = asw_override_footstep_volume.GetFloat();
}
PlayStepSound( feet, psurface, fvol, false );
}
surfacedata_t* C_ASW_Alien::GetGroundSurface()
{
//
// Find the name of the material that lies beneath the player.
//
Vector start, end;
VectorCopy( GetAbsOrigin(), start );
VectorCopy( start, end );
// Straight down
end.z -= 38; // was 64
// Fill in default values, just in case.
Ray_t ray;
ray.Init( start, end, GetCollideable()->OBBMins(), GetCollideable()->OBBMaxs() );
trace_t trace;
UTIL_TraceRay( ray, MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NPC, &trace );
if ( trace.fraction == 1.0f )
return NULL; // no ground
return physprops->GetSurfaceData( trace.surface.surfaceProps );
}
void C_ASW_Alien::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force )
{
if ( !psurface )
return;
unsigned short stepSoundName = m_bStepSideLeft ? psurface->sounds.runStepLeft : psurface->sounds.runStepRight;
m_bStepSideLeft = !m_bStepSideLeft;
if ( !stepSoundName )
return;
const char *pSoundName = physprops->GetString( stepSoundName );
CSoundParameters params;
if ( !CBaseEntity::GetParametersForSound( pSoundName, params, NULL ) )
return;
// do the surface dependent sound
CLocalPlayerFilter filter;
EmitSound_t ep;
ep.m_nChannel = CHAN_BODY;
ep.m_pSoundName = params.soundname;
ep.m_flVolume = ( asw_override_footstep_volume.GetBool() ) ? fvol : params.volume;
ep.m_SoundLevel = params.soundlevel;
ep.m_nFlags = 0;
ep.m_nPitch = params.pitch;
ep.m_pOrigin = &vecOrigin;
EmitSound( filter, entindex(), ep );
DoAlienFootstep(vecOrigin, fvol);
}
// plays alien type specific footstep sound
void C_ASW_Alien::DoAlienFootstep(Vector &vecOrigin, float fvol)
{
CSoundParameters params;
if ( !CBaseEntity::GetParametersForSound( "ASW_Drone.FootstepSoft", params, NULL ) )
return;
CLocalPlayerFilter filter;
// do the alienfleshy foot sound
EmitSound_t ep2;
ep2.m_nChannel = CHAN_AUTO;
ep2.m_pSoundName = params.soundname;
ep2.m_flVolume = fvol;
ep2.m_SoundLevel = params.soundlevel;
ep2.m_nFlags = 0;
ep2.m_nPitch = params.pitch;
ep2.m_pOrigin = &vecOrigin;
EmitSound( filter, entindex(), ep2 );
sm_flLastFootstepTime = gpGlobals->curtime;
}
C_BaseAnimating * C_ASW_Alien::BecomeRagdollOnClient( void )
{
// if we have a custom death force get it here, before the ragdoll is created
// the shield bug uses this
if ( HasCustomDeathForce() )
{
m_vecForce = GetCustomDeathForce();
}
//Msg("[C] C_ASW_Alien::BecomeRagdollOnClient on fire? %d / %d", ( GetFlags() & FL_ONFIRE ), m_bOnFire.Get());
C_BaseAnimating* pEnt = BaseClass::BecomeRagdollOnClient();
C_ASW_ClientRagdoll* pRagdoll = dynamic_cast<C_ASW_ClientRagdoll*>(pEnt);
if (pRagdoll)
{
if ( asw_alien_debug_death_style.GetBool() )
{
Msg( "'%s' C_ASW_Alien::BecomeRagdollOnClient: m_nDeathStyle = %d\n", GetClassname(), m_nDeathStyle );
}
C_ASW_Player *pPlayer = C_ASW_Player::GetLocalASWPlayer();
pRagdoll->m_nDeathStyle = m_nDeathStyle;
pRagdoll->AddEffects(EF_NOSHADOW);
// if we broke don't draw the ragdoll
if ( m_nDeathStyle == kDIE_BREAKABLE )
{
if ( asw_breakable_aliens.GetBool() )
{
pRagdoll->AddEffects(EF_NODRAW);
}
pRagdoll->m_fASWGibTime = gpGlobals->curtime + ASW_GIB_ASAP; //gpGlobals->curtime + random->RandomFloat( 0.2f, 0.4f );
}
// make this ragdoll gib RIGHT MEOW
else if ( m_nDeathStyle == kDIE_INSTAGIB )
{
// force instant gib
// this happens when an alien takes a large amount of damage
pRagdoll->m_fASWGibTime = gpGlobals->curtime + ASW_GIB_ASAP;
}
else if ( m_bOnFire.Get() )
{
pRagdoll->m_fASWGibTime = gpGlobals->curtime + random->RandomFloat( 0.3f, 0.7f );
}
else if ( IsHurler() )
{
pRagdoll->m_fASWGibTime = gpGlobals->curtime + random->RandomFloat( 4, 9 );
pRagdoll->pszGibParticleEffect = GetRagdollGibParticleEffectName();
}
else if ( IsMeleeThrown() )
{
pRagdoll->m_fASWGibTime = gpGlobals->curtime + random->RandomFloat( 4, 9 );
pRagdoll->pszGibParticleEffect = GetRagdollGibParticleEffectName();
}
else if ( m_nDeathStyle == kDIE_RAGDOLLFADE )
{
pRagdoll->m_fASWGibTime = gpGlobals->curtime + random->RandomFloat(asw_drone_fade_time_min.GetFloat(), asw_drone_fade_time_max.GetFloat());
pRagdoll->pszGibParticleEffect = GetRagdollGibParticleEffectName();
}
else
{
pRagdoll->m_fASWGibTime = gpGlobals->curtime + random->RandomFloat(asw_drone_gib_time_min.GetFloat(), asw_drone_gib_time_max.GetFloat());
pRagdoll->pszGibParticleEffect = GetRagdollGibParticleEffectName();
}
if ( m_bOnFire.Get() )
{
pRagdoll->AddFlag( FL_ONFIRE );
CNewParticleEffect *pBurningEffect = pRagdoll->ParticleProp()->Create( "ent_on_fire", PATTACH_ABSORIGIN_FOLLOW );
if (pBurningEffect)
{
Vector vecOffest1 = (pRagdoll->WorldSpaceCenter() - pRagdoll->GetAbsOrigin()) + Vector( 0, 0, 16 );
pPlayer->ParticleProp()->AddControlPoint( pBurningEffect, 1, pRagdoll, PATTACH_ABSORIGIN_FOLLOW, NULL, vecOffest1 );
}
}
else
{
if ( m_bElectroStunned )
{
pRagdoll->m_bElectroShock = true;
}
if ( pPlayer )
{
// if we're going to ragdoll, create a big blood spurt now so players get feedback about killing this alien
QAngle vecAngles;
if ( m_vecForce == vec3_origin )
{
m_vecForce = Vector( RandomFloat( 1, 100 ), RandomFloat( 1, 100 ), 100.0f );
}
VectorAngles( m_vecForce, vecAngles );
Vector vecForward, vecRight, vecUp;
AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp );
const char *pchEffectName = NULL;
switch ( m_nDeathStyle )
{
case kDIE_TUMBLEGIB:
case kDIE_RAGDOLLFADE:
pchEffectName = GetSmallDeathParticleEffectName();
break;
case kDIE_INSTAGIB:
case kDIE_BREAKABLE:
pchEffectName = GetBigDeathParticleEffectName();
break;
default:
pchEffectName = GetDeathParticleEffectName();
break;
}
CUtlReference< CNewParticleEffect > pEffect;
pEffect = pPlayer->ParticleProp()->Create( pchEffectName, PATTACH_ABSORIGIN_FOLLOW );
if ( pEffect )
{
pPlayer->ParticleProp()->AddControlPoint( pEffect, 1, pRagdoll, PATTACH_CUSTOMORIGIN );
pEffect->SetControlPoint( 1, WorldSpaceCenter() );//origin - pMarine->GetAbsOrigin()
pEffect->SetControlPointOrientation( 1, vecForward, vecRight, vecUp );
pEffect->SetControlPointEntity( 0, pRagdoll );
}
else
{
Warning( "Could not create effect for alien death: %s", pchEffectName );
}
}
if ( IsHurler() )
{
ASWHurlRagdollAtCamera( pRagdoll );
}
else if ( IsMeleeThrown() )
{
ASWMeleeThrowRagdoll( pRagdoll );
}
}
}
return pRagdoll;
}
C_ClientRagdoll *C_ASW_Alien::CreateClientRagdoll( bool bRestoring )
{
return new C_ASW_ClientRagdoll( bRestoring );
}
// shadow direction test
bool C_ASW_Alien::GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const
{
VPROF_BUDGET( "C_ASW_Alien::GetShadowCastDistance", VPROF_BUDGETGROUP_ASW_CLIENT );
if (!asw_directional_shadows.GetBool())
return false;
if (m_fLastCustomContribution > 0)
{
Vector vecDir = m_vecLastCustomDir;
vecDir += (*pDirection) * (1.0f - m_fLastCustomContribution);
vecDir.NormalizeInPlace();
pDirection->x = vecDir.x;
pDirection->y = vecDir.y;
pDirection->z = vecDir.z;
return true;
}
return false;
}
ShadowType_t C_ASW_Alien::ShadowCastType()
{
if (asw_alien_shadows.GetBool())
return BaseClass::ShadowCastType();
float fContribution = 0;
Vector vecDir = vec3_origin;
GetShadowFromFlashlight(vecDir, fContribution);
m_fLastCustomContribution = fContribution;
m_vecLastCustomDir = vecDir;
m_iLastCustomFrame = gpGlobals->framecount;
if (m_fLastCustomContribution <= 0)
{
return SHADOWS_NONE;
}
return BaseClass::ShadowCastType();
}
void C_ASW_Alien::GetShadowFromFlashlight(Vector &vecDir, float &fContribution) const
{
if (gpGlobals->framecount == m_iLastCustomFrame)
{
return;
}
if ( ASWGameResource() )
{
// go through all marines
int iMaxMarines = ASWGameResource()->GetMaxMarineResources();
for (int i=0;i<iMaxMarines;i++)
{
C_ASW_Marine_Resource *pMR = ASWGameResource()->GetMarineResource(i);
C_ASW_Marine *pMarine = pMR ? pMR->GetMarineEntity() : NULL;
if (pMarine && pMarine->m_pFlashlight) // if this is a marine with a flashlight
{
Vector diff = WorldSpaceCenter() - pMarine->EyePosition();
if (diff.Length() < 700.0f)
{
diff.NormalizeInPlace();
Vector vecMarineFacing(0,0,0);
AngleVectors(pMarine->GetAbsAngles(), &vecMarineFacing);
float dot = vecMarineFacing.Dot(diff);
if (dot > 0.2) // if the flashlight is facing us
{
vecDir += dot * diff;
fContribution += (dot - 0.2f) * 1.25f;
}
}
}
}
}
}
void C_ASW_Alien::PostDataUpdate( DataUpdateType_t updateType )
{
BaseClass::PostDataUpdate(updateType);
// If this entity was new, then latch in various values no matter what.
if ( updateType == DATA_UPDATE_CREATED )
{
// We want to think every frame.
SetNextClientThink( CLIENT_THINK_ALWAYS );
}
}
Vector C_ASW_Alien::GetLocalAutoTargetRadiusPos()
{
// drone overrides this
return m_vecLastRenderedPos;
}
void C_ASW_Alien::ClientThink()
{
BaseClass::ClientThink();
// asw temp fix demo playback
//ASWUpdateClientSideAnimation();
m_vecLastRenderedPos = WorldSpaceCenter();
m_vecAutoTargetRadiusPos = GetLocalAutoTargetRadiusPos();
if ( GetHealth() > 0 && m_bElectroStunned && m_fNextElectroStunEffect <= gpGlobals->curtime)
{
// apply electro stun effect
HACK_GETLOCALPLAYER_GUARD( "C_ASW_Alien::ClientThink FX_ElectroStun" );
FX_ElectroStun(this);
m_fNextElectroStunEffect = gpGlobals->curtime + RandomFloat( 0.3, 1.0 );
//Msg( "%f - ElectroStunEffect\n", gpGlobals->curtime );
}
UpdateFireEmitters();
C_ASW_Player* pPlayer = C_ASW_Player::GetLocalASWPlayer();
if ( pPlayer && pPlayer->IsSniperScopeActive() )
{
m_GlowObject.SetRenderFlags( true, true );
}
else
{
m_GlowObject.SetRenderFlags( false, false );
}
}
// asw - test always advancing the frames
void C_ASW_Alien::ASWUpdateClientSideAnimation()
{
if ( GetSequence() != -1 )
{
// latch old values
//OnLatchInterpolatedVariables( LATCH_ANIMATION_VAR );
// move frame forward
//FrameAdvance( 0.0f ); // 0 means to use the time we last advanced instead of a constant
CStudioHdr *hdr = GetModelPtr();
float cyclerate = hdr ? GetSequenceCycleRate( hdr, GetSequence() ) : 1.0f;
float addcycle = gpGlobals->frametime * cyclerate * m_flPlaybackRate;
float flNewCycle = GetCycle() + addcycle;
m_flAnimTime = gpGlobals->curtime;
if ( (flNewCycle < 0.0f) || (flNewCycle >= 1.0f) )
{
if (flNewCycle >= 1.0f) // asw
ReachedEndOfSequence(); // asw
if ( IsSequenceLooping( hdr, GetSequence() ) )
{
flNewCycle -= (int)(flNewCycle);
}
else
{
flNewCycle = (flNewCycle < 0.0f) ? 0.0f : 1.0f;
}
}
SetCycle( flNewCycle );
}
}
void C_ASW_Alien::UpdateFireEmitters()
{
bool bOnFire = (m_bOnFire.Get() && !IsEffectActive(EF_NODRAW));
if (bOnFire != m_bClientOnFire)
{
m_bClientOnFire = bOnFire;
if (m_bClientOnFire)
{
if ( !m_pBurningEffect )
{
m_pBurningEffect = UTIL_ASW_CreateFireEffect( this );
}
EmitSound( "ASWFire.BurningFlesh" );
}
else
{
if ( m_pBurningEffect )
{
ParticleProp()->StopEmission( m_pBurningEffect );
m_pBurningEffect = NULL;
}
StopSound("ASWFire.BurningFlesh");
if ( C_BaseEntity::IsAbsQueriesValid() )
EmitSound("ASWFire.StopBurning");
}
}
}
void C_ASW_Alien::UpdateOnRemove( void )
{
BaseClass::UpdateOnRemove();
m_bOnFire = false;
UpdateFireEmitters();
}
// aliens require extra interpolation time due to think rate
ConVar cl_alien_extra_interp( "cl_alien_extra_interp", "0.1", FCVAR_NONE, "Extra interpolation for aliens." );
float C_ASW_Alien::GetInterpolationAmount( int flags )
{
return BaseClass::GetInterpolationAmount( flags ) + cl_alien_extra_interp.GetFloat();
}