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.
1114 lines
33 KiB
1114 lines
33 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "ai_hint.h" |
|
#include "env_headcrabcanister_shared.h" |
|
#include "explode.h" |
|
#include "beam_shared.h" |
|
#include "SpriteTrail.h" |
|
#include "ar2_explosion.h" |
|
#include "SkyCamera.h" |
|
#include "smoke_trail.h" |
|
#include "ai_basenpc.h" |
|
#include "npc_headcrab.h" |
|
#include "ai_motor.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Models! |
|
//----------------------------------------------------------------------------- |
|
#define ENV_HEADCRABCANISTER_MODEL "models/props_combine/headcrabcannister01a.mdl" |
|
#define ENV_HEADCRABCANISTER_BROKEN_MODEL "models/props_combine/headcrabcannister01b.mdl" |
|
#define ENV_HEADCRABCANISTER_SKYBOX_MODEL "models/props_combine/headcrabcannister01a_skybox.mdl" |
|
#define ENV_HEADCRABCANISTER_INCOMING_SOUND_TIME 1.0f |
|
|
|
ConVar sk_env_headcrabcanister_shake_amplitude( "sk_env_headcrabcanister_shake_amplitude", "50" ); |
|
ConVar sk_env_headcrabcanister_shake_radius( "sk_env_headcrabcanister_shake_radius", "1024" ); |
|
ConVar sk_env_headcrabcanister_shake_radius_vehicle( "sk_env_headcrabcanister_shake_radius_vehicle", "2500" ); |
|
|
|
#define ENV_HEADCRABCANISTER_TRAIL_TIME 3.0f |
|
|
|
//----------------------------------------------------------------------------- |
|
// Spawn flags |
|
//----------------------------------------------------------------------------- |
|
enum |
|
{ |
|
SF_NO_IMPACT_SOUND = 0x1, |
|
SF_NO_LAUNCH_SOUND = 0x2, |
|
SF_START_IMPACTED = 0x1000, |
|
SF_LAND_AT_INITIAL_POSITION = 0x2000, |
|
SF_WAIT_FOR_INPUT_TO_OPEN = 0x4000, |
|
SF_WAIT_FOR_INPUT_TO_SPAWN_HEADCRABS = 0x8000, |
|
SF_NO_SMOKE = 0x10000, |
|
SF_NO_SHAKE = 0x20000, |
|
SF_REMOVE_ON_IMPACT = 0x40000, |
|
SF_NO_IMPACT_EFFECTS = 0x80000, |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Headcrab types |
|
//----------------------------------------------------------------------------- |
|
static const char *s_pHeadcrabClass[] = |
|
{ |
|
"npc_headcrab", |
|
"npc_headcrab_fast", |
|
"npc_headcrab_poison", |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Context think |
|
//----------------------------------------------------------------------------- |
|
static const char *s_pOpenThinkContext = "OpenThink"; |
|
static const char *s_pHeadcrabThinkContext = "HeadcrabThink"; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// HeadcrabCanister Class |
|
//----------------------------------------------------------------------------- |
|
class CEnvHeadcrabCanister : public CBaseAnimating |
|
{ |
|
DECLARE_CLASS( CEnvHeadcrabCanister, CBaseAnimating ); |
|
DECLARE_DATADESC(); |
|
DECLARE_SERVERCLASS(); |
|
|
|
public: |
|
|
|
// Initialization |
|
CEnvHeadcrabCanister(); |
|
|
|
virtual void Precache( void ); |
|
virtual void Spawn( void ); |
|
virtual void UpdateOnRemove(); |
|
|
|
virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ); |
|
|
|
private: |
|
void InputFireCanister( inputdata_t &inputdata ); |
|
void InputOpenCanister( inputdata_t &inputdata ); |
|
void InputSpawnHeadcrabs( inputdata_t &inputdata ); |
|
void InputStopSmoke( inputdata_t &inputdata ); |
|
|
|
// Think(s) |
|
void HeadcrabCanisterSkyboxThink( void ); |
|
void HeadcrabCanisterWorldThink( void ); |
|
void HeadcrabCanisterSpawnHeadcrabThink(); |
|
void HeadcrabCanisterSkyboxOnlyThink( void ); |
|
void HeadcrabCanisterSkyboxRestartThink( void ); |
|
void WaitForOpenSequenceThink(); |
|
|
|
// Place the canister in the world |
|
CSkyCamera* PlaceCanisterInWorld(); |
|
|
|
// Check for impacts |
|
void TestForCollisionsAgainstEntities( const Vector &vecEndPosition ); |
|
void TestForCollisionsAgainstWorld( const Vector &vecEndPosition ); |
|
|
|
// Figure out where we enter the world |
|
void ComputeWorldEntryPoint( Vector *pStartPosition, QAngle *pStartAngles, Vector *pStartDirection ); |
|
|
|
// Blows up! |
|
void Detonate( void ); |
|
|
|
// Landed! |
|
void SetLanded( void ); |
|
void Landed( void ); |
|
|
|
// Open! |
|
void OpenCanister( void ); |
|
void CanisterFinishedOpening(); |
|
|
|
// Set up the world model |
|
void SetupWorldModel(); |
|
|
|
// Start spawning headcrabs |
|
void StartSpawningHeadcrabs( float flDelay ); |
|
|
|
private: |
|
CNetworkVar( bool, m_bLanded ); |
|
|
|
CNetworkVarEmbedded( CEnvHeadcrabCanisterShared, m_Shared ); |
|
CHandle<CSpriteTrail> m_hTrail; |
|
CHandle<SmokeTrail> m_hSmokeTrail; |
|
int m_nHeadcrabType; |
|
int m_nHeadcrabCount; |
|
Vector m_vecImpactPosition; |
|
float m_flDamageRadius; |
|
float m_flDamage; |
|
bool m_bIncomingSoundStarted; |
|
bool m_bHasDetonated; |
|
bool m_bLaunched; |
|
bool m_bOpened; |
|
float m_flSmokeLifetime; |
|
string_t m_iszLaunchPositionName; |
|
|
|
COutputEHANDLE m_OnLaunched; |
|
COutputEvent m_OnImpacted; |
|
COutputEvent m_OnOpened; |
|
|
|
// Only for skybox only cannisters. |
|
float m_flMinRefireTime; |
|
float m_flMaxRefireTime; |
|
int m_nSkyboxCannisterCount; |
|
}; |
|
|
|
|
|
//============================================================================= |
|
// |
|
// HeadcrabCanister Functions |
|
// |
|
|
|
LINK_ENTITY_TO_CLASS( env_headcrabcanister, CEnvHeadcrabCanister ); |
|
|
|
BEGIN_DATADESC( CEnvHeadcrabCanister ) |
|
|
|
DEFINE_FIELD( m_bLanded, FIELD_BOOLEAN ), |
|
DEFINE_EMBEDDED( m_Shared ), |
|
DEFINE_FIELD( m_hTrail, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hSmokeTrail, FIELD_EHANDLE ), |
|
DEFINE_KEYFIELD( m_nHeadcrabType, FIELD_INTEGER, "HeadcrabType" ), |
|
DEFINE_KEYFIELD( m_nHeadcrabCount, FIELD_INTEGER, "HeadcrabCount" ), |
|
DEFINE_KEYFIELD( m_flSmokeLifetime, FIELD_FLOAT, "SmokeLifetime" ), |
|
DEFINE_KEYFIELD( m_iszLaunchPositionName, FIELD_STRING, "LaunchPositionName" ), |
|
DEFINE_FIELD( m_vecImpactPosition, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_bIncomingSoundStarted, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bHasDetonated, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bLaunched, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bOpened, FIELD_BOOLEAN ), |
|
DEFINE_KEYFIELD( m_flMinRefireTime, FIELD_FLOAT, "MinSkyboxRefireTime" ), |
|
DEFINE_KEYFIELD( m_flMaxRefireTime, FIELD_FLOAT, "MaxSkyboxRefireTime" ), |
|
DEFINE_KEYFIELD( m_nSkyboxCannisterCount, FIELD_INTEGER, "SkyboxCannisterCount" ), |
|
DEFINE_KEYFIELD( m_flDamageRadius, FIELD_FLOAT, "DamageRadius" ), |
|
DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "Damage" ), |
|
|
|
// Function Pointers. |
|
DEFINE_FUNCTION( HeadcrabCanisterSkyboxThink ), |
|
DEFINE_FUNCTION( HeadcrabCanisterWorldThink ), |
|
DEFINE_FUNCTION( HeadcrabCanisterSpawnHeadcrabThink ), |
|
DEFINE_FUNCTION( WaitForOpenSequenceThink ), |
|
DEFINE_FUNCTION( HeadcrabCanisterSkyboxOnlyThink ), |
|
DEFINE_FUNCTION( HeadcrabCanisterSkyboxRestartThink ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_VOID, "FireCanister", InputFireCanister ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "OpenCanister", InputOpenCanister ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "SpawnHeadcrabs", InputSpawnHeadcrabs ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "StopSmoke", InputStopSmoke ), |
|
|
|
// Outputs |
|
DEFINE_OUTPUT( m_OnLaunched, "OnLaunched" ), |
|
DEFINE_OUTPUT( m_OnImpacted, "OnImpacted" ), |
|
DEFINE_OUTPUT( m_OnOpened, "OnOpened" ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
EXTERN_SEND_TABLE(DT_EnvHeadcrabCanisterShared); |
|
|
|
IMPLEMENT_SERVERCLASS_ST( CEnvHeadcrabCanister, DT_EnvHeadcrabCanister ) |
|
SendPropDataTable( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE(DT_EnvHeadcrabCanisterShared) ), |
|
SendPropBool( SENDINFO( m_bLanded ) ), |
|
END_SEND_TABLE() |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CEnvHeadcrabCanister::CEnvHeadcrabCanister() |
|
{ |
|
m_flMinRefireTime = -1.0f; |
|
m_flMaxRefireTime = -1.0f; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Precache! |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::Precache( void ) |
|
{ |
|
BaseClass::Precache(); |
|
PrecacheModel( ENV_HEADCRABCANISTER_MODEL ); |
|
PrecacheModel( ENV_HEADCRABCANISTER_BROKEN_MODEL ); |
|
PrecacheModel( ENV_HEADCRABCANISTER_SKYBOX_MODEL ); |
|
PrecacheModel("sprites/smoke.vmt"); |
|
|
|
PrecacheScriptSound( "HeadcrabCanister.LaunchSound" ); |
|
PrecacheScriptSound( "HeadcrabCanister.AfterLanding" ); |
|
PrecacheScriptSound( "HeadcrabCanister.Explosion" ); |
|
PrecacheScriptSound( "HeadcrabCanister.IncomingSound" ); |
|
PrecacheScriptSound( "HeadcrabCanister.SkyboxExplosion" ); |
|
PrecacheScriptSound( "HeadcrabCanister.Open" ); |
|
|
|
UTIL_PrecacheOther( s_pHeadcrabClass[m_nHeadcrabType] ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Spawn! |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::Spawn( void ) |
|
{ |
|
Precache(); |
|
BaseClass::Spawn(); |
|
|
|
// Do we have a position to launch from? |
|
if ( m_iszLaunchPositionName != NULL_STRING ) |
|
{ |
|
// It doesn't have any real presence at first. |
|
SetSolid( SOLID_NONE ); |
|
|
|
m_vecImpactPosition = GetAbsOrigin(); |
|
m_bIncomingSoundStarted = false; |
|
m_bLanded = false; |
|
m_bHasDetonated = false; |
|
m_bOpened = false; |
|
} |
|
else if ( !HasSpawnFlags( SF_START_IMPACTED ) ) |
|
{ |
|
// It doesn't have any real presence at first. |
|
SetSolid( SOLID_NONE ); |
|
|
|
if ( !HasSpawnFlags( SF_LAND_AT_INITIAL_POSITION ) ) |
|
{ |
|
Vector vecForward; |
|
GetVectors( &vecForward, NULL, NULL ); |
|
vecForward *= -1.0f; |
|
|
|
trace_t trace; |
|
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecForward * 10000, MASK_NPCWORLDSTATIC, |
|
this, COLLISION_GROUP_NONE, &trace ); |
|
|
|
m_vecImpactPosition = trace.endpos; |
|
} |
|
else |
|
{ |
|
m_vecImpactPosition = GetAbsOrigin(); |
|
} |
|
|
|
m_bIncomingSoundStarted = false; |
|
m_bLanded = false; |
|
m_bHasDetonated = false; |
|
m_bOpened = false; |
|
} |
|
else |
|
{ |
|
m_bHasDetonated = true; |
|
m_bIncomingSoundStarted = true; |
|
m_bOpened = false; |
|
m_vecImpactPosition = GetAbsOrigin(); |
|
Landed(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// On remove! |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::UpdateOnRemove() |
|
{ |
|
BaseClass::UpdateOnRemove(); |
|
StopSound( "HeadcrabCanister.AfterLanding" ); |
|
if ( m_hTrail ) |
|
{ |
|
UTIL_Remove( m_hTrail ); |
|
m_hTrail = NULL; |
|
} |
|
if ( m_hSmokeTrail ) |
|
{ |
|
UTIL_Remove( m_hSmokeTrail ); |
|
m_hSmokeTrail = NULL; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Set up the world model |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::SetupWorldModel() |
|
{ |
|
SetModel( ENV_HEADCRABCANISTER_MODEL ); |
|
SetSolid( SOLID_BBOX ); |
|
|
|
float flRadius = CollisionProp()->BoundingRadius(); |
|
Vector vecMins( -flRadius, -flRadius, -flRadius ); |
|
Vector vecMaxs( flRadius, flRadius, flRadius ); |
|
SetSize( vecMins, vecMaxs ); |
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Figure out where we enter the world |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::ComputeWorldEntryPoint( Vector *pStartPosition, QAngle *pStartAngles, Vector *pStartDirection ) |
|
{ |
|
SetupWorldModel(); |
|
|
|
Vector vecForward; |
|
GetVectors( &vecForward, NULL, NULL ); |
|
|
|
// Raycast up to the place where we should start from (start raycast slightly off the ground, |
|
// since it'll be buried in the ground oftentimes) |
|
trace_t tr; |
|
CTraceFilterWorldOnly filter; |
|
UTIL_TraceLine( GetAbsOrigin() + vecForward * 100, GetAbsOrigin() + vecForward * 10000, |
|
CONTENTS_SOLID, &filter, &tr ); |
|
|
|
*pStartPosition = tr.endpos; |
|
*pStartAngles = GetAbsAngles(); |
|
VectorMultiply( vecForward, -1.0f, *pStartDirection ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Place the canister in the world |
|
//----------------------------------------------------------------------------- |
|
CSkyCamera *CEnvHeadcrabCanister::PlaceCanisterInWorld() |
|
{ |
|
CSkyCamera *pCamera = NULL; |
|
|
|
// Are we launching from a point? If so, use that point. |
|
if ( m_iszLaunchPositionName != NULL_STRING ) |
|
{ |
|
// Get the launch position entity |
|
CBaseEntity *pLaunchPos = gEntList.FindEntityByName( NULL, m_iszLaunchPositionName ); |
|
if ( !pLaunchPos ) |
|
{ |
|
Warning("%s (%s) could not find an entity matching LaunchPositionName of '%s'\n", GetEntityName().ToCStr(), GetDebugName(), STRING(m_iszLaunchPositionName) ); |
|
SUB_Remove(); |
|
} |
|
else |
|
{ |
|
SetupWorldModel(); |
|
|
|
Vector vecForward, vecImpactDirection; |
|
GetVectors( &vecForward, NULL, NULL ); |
|
VectorMultiply( vecForward, -1.0f, vecImpactDirection ); |
|
|
|
m_Shared.InitInWorld( gpGlobals->curtime, pLaunchPos->GetAbsOrigin(), GetAbsAngles(), |
|
vecImpactDirection, m_vecImpactPosition, true ); |
|
SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink ); |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
} |
|
else if ( DetectInSkybox() ) |
|
{ |
|
pCamera = GetEntitySkybox(); |
|
|
|
SetModel( ENV_HEADCRABCANISTER_SKYBOX_MODEL ); |
|
SetSolid( SOLID_NONE ); |
|
|
|
Vector vecForward; |
|
GetVectors( &vecForward, NULL, NULL ); |
|
vecForward *= -1.0f; |
|
|
|
m_Shared.InitInSkybox( gpGlobals->curtime, m_vecImpactPosition, GetAbsAngles(), vecForward, |
|
m_vecImpactPosition, pCamera->m_skyboxData.origin, pCamera->m_skyboxData.scale ); |
|
AddEFlags( EFL_IN_SKYBOX ); |
|
SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterSkyboxOnlyThink ); |
|
SetNextThink( gpGlobals->curtime + m_Shared.GetEnterWorldTime() + TICK_INTERVAL ); |
|
} |
|
else |
|
{ |
|
Vector vecStartPosition, vecDirection; |
|
QAngle vecStartAngles; |
|
ComputeWorldEntryPoint( &vecStartPosition, &vecStartAngles, &vecDirection ); |
|
|
|
// Figure out which skybox to place the entity in. |
|
pCamera = GetCurrentSkyCamera(); |
|
if ( pCamera ) |
|
{ |
|
m_Shared.InitInSkybox( gpGlobals->curtime, vecStartPosition, vecStartAngles, vecDirection, |
|
m_vecImpactPosition, pCamera->m_skyboxData.origin, pCamera->m_skyboxData.scale ); |
|
|
|
if ( m_Shared.IsInSkybox() ) |
|
{ |
|
SetModel( ENV_HEADCRABCANISTER_SKYBOX_MODEL ); |
|
SetSolid( SOLID_NONE ); |
|
AddEFlags( EFL_IN_SKYBOX ); |
|
SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterSkyboxThink ); |
|
SetNextThink( gpGlobals->curtime + m_Shared.GetEnterWorldTime() ); |
|
} |
|
else |
|
{ |
|
SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink ); |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
} |
|
else |
|
{ |
|
m_Shared.InitInWorld( gpGlobals->curtime, vecStartPosition, vecStartAngles, |
|
vecDirection, m_vecImpactPosition ); |
|
SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink ); |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
} |
|
|
|
Vector vecEndPosition; |
|
QAngle vecEndAngles; |
|
m_Shared.GetPositionAtTime( gpGlobals->curtime, vecEndPosition, vecEndAngles ); |
|
SetAbsOrigin( vecEndPosition ); |
|
SetAbsAngles( vecEndAngles ); |
|
|
|
return pCamera; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Fires the canister! |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::InputFireCanister( inputdata_t &inputdata ) |
|
{ |
|
if (m_bLaunched) |
|
return; |
|
|
|
m_bLaunched = true; |
|
|
|
if ( HasSpawnFlags( SF_START_IMPACTED ) ) |
|
{ |
|
StartSpawningHeadcrabs( 0.01f ); |
|
return; |
|
} |
|
|
|
// Play a firing sound |
|
CPASAttenuationFilter filter( this, ATTN_NONE ); |
|
|
|
if ( !HasSpawnFlags( SF_NO_LAUNCH_SOUND ) ) |
|
{ |
|
EmitSound( filter, entindex(), "HeadcrabCanister.LaunchSound" ); |
|
} |
|
|
|
// Place the canister |
|
CSkyCamera *pCamera = PlaceCanisterInWorld(); |
|
|
|
// Hook up a smoke trail |
|
m_hTrail = CSpriteTrail::SpriteTrailCreate( "sprites/smoke.vmt", GetAbsOrigin(), true ); |
|
m_hTrail->SetTransparency( kRenderTransAdd, 224, 224, 255, 255, kRenderFxNone ); |
|
m_hTrail->SetAttachment( this, 0 ); |
|
m_hTrail->SetStartWidth( 32.0 ); |
|
m_hTrail->SetEndWidth( 200.0 ); |
|
m_hTrail->SetStartWidthVariance( 15.0f ); |
|
m_hTrail->SetTextureResolution( 0.002 ); |
|
m_hTrail->SetLifeTime( ENV_HEADCRABCANISTER_TRAIL_TIME ); |
|
m_hTrail->SetMinFadeLength( 1000.0f ); |
|
|
|
if ( pCamera && m_Shared.IsInSkybox() ) |
|
{ |
|
m_hTrail->SetSkybox( pCamera->m_skyboxData.origin, pCamera->m_skyboxData.scale ); |
|
} |
|
|
|
// Fire that output! |
|
m_OnLaunched.Set( this, this, this ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Opens the canister! |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::InputOpenCanister( inputdata_t &inputdata ) |
|
{ |
|
if ( m_bLanded && !m_bOpened && HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_OPEN ) ) |
|
{ |
|
OpenCanister(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Spawns headcrabs |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::InputSpawnHeadcrabs( inputdata_t &inputdata ) |
|
{ |
|
if ( m_bLanded && m_bOpened && HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_SPAWN_HEADCRABS ) ) |
|
{ |
|
StartSpawningHeadcrabs( 0.01f ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::InputStopSmoke( inputdata_t &inputdata ) |
|
{ |
|
if ( m_hSmokeTrail != NULL ) |
|
{ |
|
UTIL_Remove( m_hSmokeTrail ); |
|
m_hSmokeTrail = NULL; |
|
} |
|
} |
|
|
|
//============================================================================= |
|
// |
|
// Enumerator for swept bbox collision. |
|
// |
|
class CCollideList : public IEntityEnumerator |
|
{ |
|
public: |
|
CCollideList( Ray_t *pRay, CBaseEntity* pIgnoreEntity, int nContentsMask ) : |
|
m_Entities( 0, 32 ), m_pIgnoreEntity( pIgnoreEntity ), |
|
m_nContentsMask( nContentsMask ), m_pRay(pRay) {} |
|
|
|
virtual bool EnumEntity( IHandleEntity *pHandleEntity ) |
|
{ |
|
// Don't bother with the ignore entity. |
|
if ( pHandleEntity == m_pIgnoreEntity ) |
|
return true; |
|
|
|
Assert( pHandleEntity ); |
|
|
|
trace_t tr; |
|
enginetrace->ClipRayToEntity( *m_pRay, m_nContentsMask, pHandleEntity, &tr ); |
|
if (( tr.fraction < 1.0f ) || (tr.startsolid) || (tr.allsolid)) |
|
{ |
|
CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); |
|
m_Entities.AddToTail( pEntity ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
CUtlVector<CBaseEntity*> m_Entities; |
|
|
|
private: |
|
CBaseEntity *m_pIgnoreEntity; |
|
int m_nContentsMask; |
|
Ray_t *m_pRay; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Test for impact! |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::TestForCollisionsAgainstEntities( const Vector &vecEndPosition ) |
|
{ |
|
// Debugging!! |
|
// NDebugOverlay::Box( GetAbsOrigin(), m_vecMin * 0.5f, m_vecMax * 0.5f, 255, 255, 0, 0, 5 ); |
|
// NDebugOverlay::Box( vecEndPosition, m_vecMin, m_vecMax, 255, 0, 0, 0, 5 ); |
|
|
|
float flRadius = CollisionProp()->BoundingRadius(); |
|
Vector vecMins( -flRadius, -flRadius, -flRadius ); |
|
Vector vecMaxs( flRadius, flRadius, flRadius ); |
|
|
|
Ray_t ray; |
|
ray.Init( GetAbsOrigin(), vecEndPosition, vecMins, vecMaxs ); |
|
|
|
CCollideList collideList( &ray, this, MASK_SOLID ); |
|
enginetrace->EnumerateEntities( ray, false, &collideList ); |
|
|
|
float flDamage = m_flDamage; |
|
|
|
// Now get each entity and react accordinly! |
|
for( int iEntity = collideList.m_Entities.Count(); --iEntity >= 0; ) |
|
{ |
|
CBaseEntity *pEntity = collideList.m_Entities[iEntity]; |
|
Vector vecForceDir = m_Shared.m_vecDirection; |
|
|
|
// Check for a physics object and apply force! |
|
IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); |
|
if ( pPhysObject ) |
|
{ |
|
float flMass = PhysGetEntityMass( pEntity ); |
|
vecForceDir *= flMass * 750; |
|
pPhysObject->ApplyForceCenter( vecForceDir ); |
|
} |
|
|
|
if ( pEntity->m_takedamage && ( m_flDamage != 0.0f ) ) |
|
{ |
|
CTakeDamageInfo info( this, this, flDamage, DMG_BLAST ); |
|
CalculateExplosiveDamageForce( &info, vecForceDir, pEntity->GetAbsOrigin() ); |
|
pEntity->TakeDamage( info ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Test for impact! |
|
//----------------------------------------------------------------------------- |
|
#define INNER_RADIUS_FRACTION 0.25f |
|
|
|
void CEnvHeadcrabCanister::TestForCollisionsAgainstWorld( const Vector &vecEndPosition ) |
|
{ |
|
// Splash damage! |
|
// Iterate on all entities in the vicinity. |
|
float flDamageRadius = m_flDamageRadius; |
|
float flDamage = m_flDamage; |
|
|
|
CBaseEntity *pEntity; |
|
for ( CEntitySphereQuery sphere( vecEndPosition, flDamageRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) |
|
{ |
|
if ( pEntity == this ) |
|
continue; |
|
|
|
if ( !pEntity->IsSolid() ) |
|
continue; |
|
|
|
// Get distance to object and use it as a scale value. |
|
Vector vecSegment; |
|
VectorSubtract( pEntity->GetAbsOrigin(), vecEndPosition, vecSegment ); |
|
float flDistance = VectorNormalize( vecSegment ); |
|
|
|
float flFactor = 1.0f / ( flDamageRadius * (INNER_RADIUS_FRACTION - 1) ); |
|
flFactor *= flFactor; |
|
float flScale = flDistance - flDamageRadius; |
|
flScale *= flScale * flFactor; |
|
if ( flScale > 1.0f ) |
|
{ |
|
flScale = 1.0f; |
|
} |
|
|
|
// Check for a physics object and apply force! |
|
Vector vecForceDir = vecSegment; |
|
IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject(); |
|
if ( pPhysObject ) |
|
{ |
|
// Send it flying!!! |
|
float flMass = PhysGetEntityMass( pEntity ); |
|
vecForceDir *= flMass * 750 * flScale; |
|
pPhysObject->ApplyForceCenter( vecForceDir ); |
|
} |
|
|
|
if ( pEntity->m_takedamage && ( m_flDamage != 0.0f ) ) |
|
{ |
|
CTakeDamageInfo info( this, this, flDamage * flScale, DMG_BLAST ); |
|
CalculateExplosiveDamageForce( &info, vecSegment, pEntity->GetAbsOrigin() ); |
|
pEntity->TakeDamage( info ); |
|
} |
|
|
|
if ( pEntity->IsPlayer() && !(static_cast<CBasePlayer*>(pEntity)->IsInAVehicle()) ) |
|
{ |
|
if (vecSegment.z < 0.1f) |
|
{ |
|
vecSegment.z = 0.1f; |
|
VectorNormalize( vecSegment ); |
|
} |
|
float flAmount = SimpleSplineRemapVal( flScale, 0.0f, 1.0f, 250.0f, 1000.0f ); |
|
pEntity->ApplyAbsVelocityImpulse( vecSegment * flAmount ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Headcrab creation |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink() |
|
{ |
|
Vector vecSpawnPosition; |
|
QAngle vecSpawnAngles; |
|
|
|
--m_nHeadcrabCount; |
|
|
|
int nHeadCrabAttachment = LookupAttachment( "headcrab" ); |
|
if ( GetAttachment( nHeadCrabAttachment, vecSpawnPosition, vecSpawnAngles ) ) |
|
{ |
|
CBaseEntity *pEnt = CreateEntityByName( s_pHeadcrabClass[m_nHeadcrabType] ); |
|
CBaseHeadcrab *pHeadCrab = assert_cast<CBaseHeadcrab*>(pEnt); |
|
|
|
// Necessary to get it to eject properly (don't allow the NPC |
|
// to override the spawn position specified). |
|
pHeadCrab->AddSpawnFlags( SF_NPC_FALL_TO_GROUND ); |
|
|
|
// So we don't collide with the canister |
|
// NOTE: Hierarchical attachment is necessary here to get the animations to work |
|
pHeadCrab->SetOwnerEntity( this ); |
|
DispatchSpawn( pHeadCrab ); |
|
pHeadCrab->SetParent( this, nHeadCrabAttachment ); |
|
pHeadCrab->SetLocalOrigin( vec3_origin ); |
|
pHeadCrab->SetLocalAngles( vec3_angle ); |
|
pHeadCrab->CrawlFromCanister(); |
|
} |
|
|
|
if ( m_nHeadcrabCount != 0 ) |
|
{ |
|
float flWaitTime = random->RandomFloat( 1.0f, 2.0f ); |
|
SetContextThink( &CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink, gpGlobals->curtime + flWaitTime, s_pHeadcrabThinkContext ); |
|
} |
|
else |
|
{ |
|
SetContextThink( NULL, gpGlobals->curtime, s_pHeadcrabThinkContext ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Start spawning headcrabs |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::StartSpawningHeadcrabs( float flDelay ) |
|
{ |
|
if ( !m_bLanded || !m_bOpened || m_nHeadcrabCount == 0 ) |
|
return; |
|
|
|
if ( m_nHeadcrabCount != 0 ) |
|
{ |
|
SetContextThink( &CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink, gpGlobals->curtime + flDelay, s_pHeadcrabThinkContext ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Canister finished opening |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::CanisterFinishedOpening( void ) |
|
{ |
|
ResetSequence( LookupSequence( "idle_open" ) ); |
|
m_OnOpened.FireOutput( this, this, 0 ); |
|
m_bOpened = true; |
|
SetContextThink( NULL, gpGlobals->curtime, s_pOpenThinkContext ); |
|
|
|
if ( !HasSpawnFlags( SF_START_IMPACTED ) ) |
|
{ |
|
if ( !HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_SPAWN_HEADCRABS ) ) |
|
{ |
|
StartSpawningHeadcrabs( 3.0f ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finish the opening sequence |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::WaitForOpenSequenceThink() |
|
{ |
|
StudioFrameAdvance(); |
|
if ( ( GetSequence() == LookupSequence( "open" ) ) && IsSequenceFinished() ) |
|
{ |
|
CanisterFinishedOpening(); |
|
} |
|
else |
|
{ |
|
SetContextThink( &CEnvHeadcrabCanister::WaitForOpenSequenceThink, gpGlobals->curtime + 0.01f, s_pOpenThinkContext ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Open the canister! |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::OpenCanister( void ) |
|
{ |
|
if ( m_bOpened ) |
|
return; |
|
|
|
int nOpenSequence = LookupSequence( "open" ); |
|
if ( nOpenSequence != ACT_INVALID ) |
|
{ |
|
EmitSound( "HeadcrabCanister.Open" ); |
|
|
|
ResetSequence( nOpenSequence ); |
|
SetContextThink( &CEnvHeadcrabCanister::WaitForOpenSequenceThink, gpGlobals->curtime + 0.01f, s_pOpenThinkContext ); |
|
} |
|
else |
|
{ |
|
CanisterFinishedOpening(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::SetLanded( void ) |
|
{ |
|
SetAbsOrigin( m_vecImpactPosition ); |
|
SetModel( ENV_HEADCRABCANISTER_BROKEN_MODEL ); |
|
SetMoveType( MOVETYPE_NONE ); |
|
SetSolid( SOLID_VPHYSICS ); |
|
VPhysicsInitStatic(); |
|
|
|
IncrementInterpolationFrame(); |
|
m_bLanded = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Landed! |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::Landed( void ) |
|
{ |
|
EmitSound( "HeadcrabCanister.AfterLanding" ); |
|
|
|
// Lock us now that we've stopped |
|
SetLanded(); |
|
|
|
// Hook the follow trail to the lead of the canister (which should be buried) |
|
// to hide problems with the edge of the follow trail |
|
if (m_hTrail) |
|
{ |
|
m_hTrail->SetAttachment( this, LookupAttachment("trail") ); |
|
} |
|
|
|
// Start smoke, unless we don't want it |
|
if ( !HasSpawnFlags( SF_NO_SMOKE ) ) |
|
{ |
|
// Create the smoke trail to obscure the headcrabs |
|
m_hSmokeTrail = SmokeTrail::CreateSmokeTrail(); |
|
m_hSmokeTrail->FollowEntity( this, "smoke" ); |
|
|
|
m_hSmokeTrail->m_SpawnRate = 8; |
|
m_hSmokeTrail->m_ParticleLifetime = 2.0f; |
|
|
|
m_hSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f ); |
|
m_hSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 ); |
|
|
|
m_hSmokeTrail->m_StartSize = 32; |
|
m_hSmokeTrail->m_EndSize = 64; |
|
m_hSmokeTrail->m_SpawnRadius= 8; |
|
m_hSmokeTrail->m_MinSpeed = 0; |
|
m_hSmokeTrail->m_MaxSpeed = 8; |
|
m_hSmokeTrail->m_MinDirectedSpeed = 32; |
|
m_hSmokeTrail->m_MaxDirectedSpeed = 64; |
|
m_hSmokeTrail->m_Opacity = 0.35f; |
|
|
|
m_hSmokeTrail->SetLifetime( m_flSmokeLifetime ); |
|
} |
|
|
|
SetThink( NULL ); |
|
|
|
if ( !HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_OPEN ) ) |
|
{ |
|
if ( HasSpawnFlags( SF_START_IMPACTED ) ) |
|
{ |
|
CanisterFinishedOpening( ); |
|
} |
|
else |
|
{ |
|
OpenCanister(); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates the explosion effect |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::Detonate( ) |
|
{ |
|
// Send the impact output |
|
m_OnImpacted.FireOutput( this, this, 0 ); |
|
|
|
if ( !HasSpawnFlags( SF_NO_IMPACT_SOUND ) ) |
|
{ |
|
StopSound( "HeadcrabCanister.IncomingSound" ); |
|
EmitSound( "HeadcrabCanister.Explosion" ); |
|
} |
|
|
|
// If we're supposed to be removed, do that now |
|
if ( HasSpawnFlags( SF_REMOVE_ON_IMPACT ) ) |
|
{ |
|
SetAbsOrigin( m_vecImpactPosition ); |
|
SetModel( ENV_HEADCRABCANISTER_BROKEN_MODEL ); |
|
SetMoveType( MOVETYPE_NONE ); |
|
IncrementInterpolationFrame(); |
|
m_bLanded = true; |
|
|
|
// Become invisible so our trail can finish up |
|
AddEffects( EF_NODRAW ); |
|
SetSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
SetThink( &CEnvHeadcrabCanister::SUB_Remove ); |
|
SetNextThink( gpGlobals->curtime + ENV_HEADCRABCANISTER_TRAIL_TIME ); |
|
|
|
return; |
|
} |
|
|
|
// Test for damaging things |
|
TestForCollisionsAgainstWorld( m_vecImpactPosition ); |
|
|
|
// Shake the screen unless flagged otherwise |
|
if ( !HasSpawnFlags( SF_NO_SHAKE ) ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); |
|
|
|
// If the player is on foot, then do a more limited shake |
|
float shakeRadius = ( pPlayer && pPlayer->IsInAVehicle() ) ? sk_env_headcrabcanister_shake_radius_vehicle.GetFloat() : sk_env_headcrabcanister_shake_radius.GetFloat(); |
|
|
|
UTIL_ScreenShake( m_vecImpactPosition, sk_env_headcrabcanister_shake_amplitude.GetFloat(), 150.0, 1.0, shakeRadius, SHAKE_START ); |
|
} |
|
|
|
// Do explosion effects |
|
if ( !HasSpawnFlags( SF_NO_IMPACT_EFFECTS ) ) |
|
{ |
|
// Normal explosion |
|
ExplosionCreate( m_vecImpactPosition, GetAbsAngles(), this, 50.0f, 500.0f, |
|
SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSOUND, 1300.0f ); |
|
|
|
// Dust explosion |
|
AR2Explosion *pExplosion = AR2Explosion::CreateAR2Explosion( m_vecImpactPosition ); |
|
|
|
if( pExplosion ) |
|
{ |
|
pExplosion->SetLifetime(10); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This think function simulates (moves/collides) the HeadcrabCanister while in |
|
// the world. |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::HeadcrabCanisterWorldThink( void ) |
|
{ |
|
// Get the current time. |
|
float flTime = gpGlobals->curtime; |
|
|
|
Vector vecStartPosition = GetAbsOrigin(); |
|
|
|
// Update HeadcrabCanister position for swept collision test. |
|
Vector vecEndPosition; |
|
QAngle vecEndAngles; |
|
m_Shared.GetPositionAtTime( flTime, vecEndPosition, vecEndAngles ); |
|
|
|
if ( !m_bIncomingSoundStarted && !HasSpawnFlags( SF_NO_IMPACT_SOUND ) ) |
|
{ |
|
float flDistSq = ENV_HEADCRABCANISTER_INCOMING_SOUND_TIME * m_Shared.m_flFlightSpeed; |
|
flDistSq *= flDistSq; |
|
if ( vecEndPosition.DistToSqr(m_vecImpactPosition) <= flDistSq ) |
|
{ |
|
// Figure out if we're close enough to play the incoming sound |
|
EmitSound( "HeadcrabCanister.IncomingSound" ); |
|
m_bIncomingSoundStarted = true; |
|
} |
|
} |
|
|
|
TestForCollisionsAgainstEntities( vecEndPosition ); |
|
if ( m_Shared.DidImpact( flTime ) ) |
|
{ |
|
if ( !m_bHasDetonated ) |
|
{ |
|
Detonate(); |
|
m_bHasDetonated = true; |
|
} |
|
|
|
if ( !HasSpawnFlags( SF_REMOVE_ON_IMPACT ) ) |
|
{ |
|
Landed(); |
|
} |
|
|
|
return; |
|
} |
|
|
|
// Always move full movement. |
|
SetAbsOrigin( vecEndPosition ); |
|
|
|
// Touch triggers along the way |
|
PhysicsTouchTriggers( &vecStartPosition ); |
|
|
|
SetNextThink( gpGlobals->curtime + 0.2f ); |
|
SetAbsAngles( vecEndAngles ); |
|
|
|
if ( !m_bHasDetonated ) |
|
{ |
|
if ( vecEndPosition.DistToSqr( m_vecImpactPosition ) < BoundingRadius() * BoundingRadius() ) |
|
{ |
|
Detonate(); |
|
m_bHasDetonated = true; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This think function should be called at the time when the HeadcrabCanister |
|
// will be leaving the skybox and entering the world. |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::HeadcrabCanisterSkyboxThink( void ) |
|
{ |
|
// Use different position computation |
|
m_Shared.ConvertFromSkyboxToWorld(); |
|
|
|
Vector vecEndPosition; |
|
QAngle vecEndAngles; |
|
m_Shared.GetPositionAtTime( gpGlobals->curtime, vecEndPosition, vecEndAngles ); |
|
UTIL_SetOrigin( this, vecEndPosition ); |
|
SetAbsAngles( vecEndAngles ); |
|
RemoveEFlags( EFL_IN_SKYBOX ); |
|
|
|
// Switch to the actual-scale model |
|
SetupWorldModel(); |
|
|
|
// Futz with the smoke trail to get it working across the boundary |
|
m_hTrail->SetSkybox( vec3_origin, 1.0f ); |
|
|
|
// Now we start looking for collisions |
|
SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink ); |
|
SetNextThink( gpGlobals->curtime + 0.01f ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This stops its motion in the skybox |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::HeadcrabCanisterSkyboxOnlyThink( void ) |
|
{ |
|
Vector vecEndPosition; |
|
QAngle vecEndAngles; |
|
m_Shared.GetPositionAtTime( gpGlobals->curtime, vecEndPosition, vecEndAngles ); |
|
UTIL_SetOrigin( this, vecEndPosition ); |
|
SetAbsAngles( vecEndAngles ); |
|
|
|
if ( !HasSpawnFlags( SF_NO_IMPACT_SOUND ) ) |
|
{ |
|
CPASAttenuationFilter filter( this, ATTN_NONE ); |
|
EmitSound( filter, entindex(), "HeadcrabCanister.SkyboxExplosion" ); |
|
} |
|
|
|
if ( m_nSkyboxCannisterCount != 0 ) |
|
{ |
|
if ( --m_nSkyboxCannisterCount <= 0 ) |
|
{ |
|
SetThink( NULL ); |
|
return; |
|
} |
|
} |
|
|
|
float flRefireTime = random->RandomFloat( m_flMinRefireTime, m_flMaxRefireTime ) + ENV_HEADCRABCANISTER_TRAIL_TIME; |
|
SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterSkyboxRestartThink ); |
|
SetNextThink( gpGlobals->curtime + flRefireTime ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// This will re-fire the headcrab cannister |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::HeadcrabCanisterSkyboxRestartThink( void ) |
|
{ |
|
if ( m_hTrail ) |
|
{ |
|
UTIL_Remove( m_hTrail ); |
|
m_hTrail = NULL; |
|
} |
|
|
|
m_bLaunched = false; |
|
|
|
inputdata_t data; |
|
InputFireCanister( data ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pInfo - |
|
// bAlways - |
|
//----------------------------------------------------------------------------- |
|
void CEnvHeadcrabCanister::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ) |
|
{ |
|
// Are we already marked for transmission? |
|
if ( pInfo->m_pTransmitEdict->Get( entindex() ) ) |
|
return; |
|
|
|
BaseClass::SetTransmit( pInfo, bAlways ); |
|
|
|
// Make our smoke trail always come with us |
|
if ( m_hSmokeTrail ) |
|
{ |
|
m_hSmokeTrail->SetTransmit( pInfo, bAlways ); |
|
} |
|
}
|
|
|