Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.

861 lines
23 KiB

5 years ago
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "econ_wearable.h"
#include "vcollide_parse.h"
#ifdef CLIENT_DLL
#include "functionproxy.h"
#include "c_te_effect_dispatch.h"
#endif // CLIENT_DLL
#ifdef TF_CLIENT_DLL
#include "c_team.h"
#include "tf_shareddefs.h"
#include "tf_weapon_jar.h"
#include "c_tf_player.h"
#endif // TF_CLIENT_DLL
#ifdef TF_DLL
#include "tf_player.h"
#endif // TF_DLL
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
LINK_ENTITY_TO_CLASS( wearable_item, CEconWearable );
IMPLEMENT_NETWORKCLASS_ALIASED( EconWearable, DT_WearableItem )
// Network Table --
BEGIN_NETWORK_TABLE( CEconWearable, DT_WearableItem )
END_NETWORK_TABLE()
// -- Network Table
// Data Desc --
BEGIN_DATADESC( CEconWearable )
END_DATADESC()
// -- Data Desc
PRECACHE_REGISTER( wearable_item );
IMPLEMENT_NETWORKCLASS_ALIASED( TFWearableItem, DT_TFWearableItem )
// Network Table --
BEGIN_NETWORK_TABLE( CTFWearableItem, DT_TFWearableItem )
END_NETWORK_TABLE()
// -- Network Table
// Data Desc --
BEGIN_DATADESC( CTFWearableItem )
END_DATADESC()
// -- Data Desc
//-----------------------------------------------------------------------------
// SHARED CODE
//-----------------------------------------------------------------------------
CEconWearable::CEconWearable()
{
m_bAlwaysAllow = false;
};
void CEconWearable::InternalSetPlayerDisplayModel( void )
{
int iClass = 0;
int iTeam = 0;
#if defined( TF_DLL ) || defined( TF_CLIENT_DLL )
CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() );
if ( pTFPlayer )
{
iClass = pTFPlayer->GetPlayerClass()->GetClassIndex();
iTeam = pTFPlayer->GetTeamNumber();
}
#endif // defined( TF_DLL ) || defined( TF_CLIENT_DLL )
// Set our model to the player model
CEconItemView *pItem = GetAttributeContainer()->GetItem();
if ( pItem && pItem->IsValid() )
{
const char *pszPlayerDisplayModel = pItem->GetPlayerDisplayModel( iClass, iTeam );
if ( pszPlayerDisplayModel )
{
if ( pItem->GetStaticData()->IsContentStreamable() )
{
modelinfo->RegisterDynamicModel( pszPlayerDisplayModel, IsClient() );
if ( pItem->GetVisionFilteredDisplayModel() && pItem->GetVisionFilteredDisplayModel()[ 0 ] != '\0' )
{
modelinfo->RegisterDynamicModel( pItem->GetVisionFilteredDisplayModel(), IsClient() );
}
}
SetModel( pszPlayerDisplayModel );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Set up the item. GC data may not be available here depending on
// where we're called from.
//-----------------------------------------------------------------------------
void CEconWearable::Spawn( void )
{
InitializeAttributes();
Precache();
InternalSetPlayerDisplayModel();
BaseClass::Spawn();
AddEffects( EF_BONEMERGE );
AddEffects( EF_BONEMERGE_FASTCULL );
#if !defined( CLIENT_DLL )
SetCollisionGroup( COLLISION_GROUP_WEAPON );
SetBlocksLOS( false );
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Player touches the item. Currently wearables don't appear in the
// world, so this is only called directly during equipment assignment.
//-----------------------------------------------------------------------------
void CEconWearable::GiveTo( CBaseEntity *pOther )
{
CBasePlayer *pPlayer = ToBasePlayer(pOther);
if ( !pPlayer )
return;
#if !defined( CLIENT_DLL )
pPlayer->EquipWearable( this );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEconWearable::RemoveFrom( CBaseEntity *pOther )
{
CBasePlayer *pPlayer = ToBasePlayer(pOther);
if ( !pPlayer )
return;
#if !defined( CLIENT_DLL )
pPlayer->RemoveWearable( this );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CEconWearable::GetSkin( void )
{
CEconItemView *pItem = GetAttributeContainer()->GetItem(); // Safe. Checked in base class call.
if ( pItem )
{
int iSkin = pItem->GetSkin( GetTeamNumber() );
if ( iSkin > -1 )
{
return iSkin;
}
}
return ( GetTeamNumber() == (LAST_SHARED_TEAM+1) ) ? 0 : 1;
}
//-----------------------------------------------------------------------------
// Purpose: Attaches the item to the player.
//-----------------------------------------------------------------------------
void CEconWearable::Equip( CBasePlayer* pOwner )
{
if ( !CanEquip( pOwner ) )
{
RemoveFrom( pOwner );
return;
}
SetTouch( NULL );
SetAbsVelocity( vec3_origin );
CBaseEntity *pFollowEntity = pOwner;
if ( IsViewModelWearable() )
{
pFollowEntity = pOwner->GetViewModel();
}
FollowEntity( pFollowEntity, true );
SetOwnerEntity( pOwner );
ReapplyProvision();
ChangeTeam( pOwner->GetTeamNumber() );
m_nSkin = GetSkin();
#ifdef GAME_DLL
UpdateModelToClass();
UpdateBodygroups( pOwner, true );
PlayAnimForPlaybackEvent( WAP_ON_SPAWN );
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Remove item from the player.
//-----------------------------------------------------------------------------
void CEconWearable::UnEquip( CBasePlayer* pOwner )
{
#ifdef CLIENT_DLL
SetParticleSystemsVisible( PARTICLE_SYSTEM_STATE_NOT_VISIBLE );
#endif
#ifdef GAME_DLL
UpdateBodygroups( pOwner, false );
#endif
StopFollowingEntity();
SetOwnerEntity( NULL );
ReapplyProvision();
}
/*
//-----------------------------------------------------------------------------
// Purpose: Hides or shows masked bodygroups associated with this item.
//-----------------------------------------------------------------------------
bool CEconWearable::UpdateBodygroups( CBaseCombatCharacter* pOwner, int iState )
{
if ( !pOwner )
return false;
CAttributeContainer *pCont = GetAttributeContainer();
if ( !pCont )
return false;
CEconItemView *pItem = pCont->GetItem();
if ( !pItem )
return false;
int iTeam = pOwner->GetTeamNumber();
int iNumBodyGroups = pItem->GetNumModifiedBodyGroups( iTeam );
for ( int i=0; i<iNumBodyGroups; ++i )
{
int iBody = 0;
const char *pszBodyGroup = pItem->GetModifiedBodyGroup( iTeam, i, iBody );
int iBodyGroup = pOwner->FindBodygroupByName( pszBodyGroup );
if ( iBodyGroup == -1 )
continue;
pOwner->SetBodygroup( iBodyGroup, iState );
}
return true;
}
*/
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEconWearable::OnWearerDeath( void )
{
#ifdef CLIENT_DLL
UpdateParticleSystems();
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CEconWearable::GetDropType()
{
CAttributeContainer *pCont = GetAttributeContainer();
if ( !pCont )
return 0;
CEconItemView *pItem = pCont->GetItem();
if ( pItem )
return pItem->GetDropType();
else
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Ensures that a player's correct body groups are enabled on client respawn.
//-----------------------------------------------------------------------------
void CEconWearable::UpdateWearableBodyGroups( CBasePlayer* pPlayer )
{
if ( !pPlayer )
return;
for ( int i=0; i<pPlayer->GetNumWearables(); ++i )
{
CEconWearable* pItem = pPlayer->GetWearable(i);
if ( !pItem )
continue;
// Dynamic models which are not yet rendering do not modify bodygroups
if ( pItem->IsDynamicModelLoading() )
continue;
// On the client, ignore items that aren't valid.
#ifdef TF_CLIENT_DLL
if ( pItem->EntityDeemedInvalid() )
continue;
#endif
int nVisibleState = 1;
#ifdef TF_CLIENT_DLL
if ( pItem->ShouldHideForVisionFilterFlags() )
{
// Items that shouldn't draw (pyro-vision filtered) shouldn't change any body group states
// unless they have no model (hatless hats)
nVisibleState = 0;
}
#endif
pItem->UpdateBodygroups( pPlayer, nVisibleState );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFWearableItem::CTFWearableItem()
{
}
//-----------------------------------------------------------------------------
// SERVER ONLY CODE
//-----------------------------------------------------------------------------
#if defined( GAME_DLL )
#endif
//-----------------------------------------------------------------------------
// CLIENT ONLY CODE
//-----------------------------------------------------------------------------
#if defined( CLIENT_DLL )
//-----------------------------------------------------------------------------
// Purpose: Mirror should draw logic.
//-----------------------------------------------------------------------------
ShadowType_t CEconWearable::ShadowCastType()
{
if ( ShouldDraw() )
{
return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC;
}
return SHADOWS_NONE;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CEconWearable::ShouldDraw( void )
{
CBasePlayer *pPlayerOwner = ToBasePlayer( GetOwnerEntity() );
if ( !pPlayerOwner )
{
return false;
}
bool bUseViewModel = !pPlayerOwner->ShouldDrawThisPlayer();
// Don't show view models if we're drawing the real player, and don't show non view models if using view models.
if ( bUseViewModel )
{
// VM mode.
if ( !IsViewModelWearable() )
{
return false;
}
}
else
{
// Non-viewmodel mode.
if ( IsViewModelWearable() )
{
return false;
}
}
if ( !ShouldDrawWhenPlayerIsDead() && !pPlayerOwner->IsAlive() )
{
return false;
}
if ( pPlayerOwner->GetTeamNumber() == TEAM_SPECTATOR )
{
return false;
}
return BaseClass::ShouldDraw();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEconWearable::OnDataChanged( DataUpdateType_t updateType )
{
BaseClass::OnDataChanged( updateType );
if ( updateType == DATA_UPDATE_CREATED )
{
SetNextClientThink( CLIENT_THINK_ALWAYS );
}
// Update our visibility in case our parents' has changed.
UpdateVisibility();
UpdateParticleSystems();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEconWearable::ClientThink( void )
{
BaseClass::ClientThink();
UpdateParticleSystems();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CEconWearable::ShouldDrawParticleSystems( void )
{
// Make sure the entity we're attaching to is being drawn
CBasePlayer *pPlayerOwner = ToBasePlayer( GetOwnerEntity() );
if ( !pPlayerOwner )
{
Assert ( "CEconWearable has no owner?" ); // Not sure what this means - is is visible or not?
return false;
}
if ( pPlayerOwner->ShouldDrawThisPlayer() )
{
return true;
}
return false;
}
RenderGroup_t CEconWearable::GetRenderGroup()
{
if ( IsViewModelWearable() )
return RENDER_GROUP_VIEW_MODEL_TRANSLUCENT;
return BaseClass::GetRenderGroup();
}
//-----------------------------------------------------------------------------
// Purpose: Wearable tint colors
//-----------------------------------------------------------------------------
class CProxyItemTintColor : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
Vector vResult = Vector( 0, 0, 0 );
if ( pC_BaseEntity )
{
CEconItemView *pScriptItem = NULL;
IClientRenderable *pRend = (IClientRenderable *)pC_BaseEntity;
C_BaseEntity *pEntity = pRend->GetIClientUnknown()->GetBaseEntity();
if ( pEntity )
{
CEconEntity *pItem = dynamic_cast< CEconEntity* >( pEntity );
if ( pItem )
{
pScriptItem = pItem->GetAttributeContainer()->GetItem();
}
else if ( pEntity->GetOwnerEntity() )
{
// Try the owner (for viewmodels, etc).
pEntity = pEntity->GetOwnerEntity();
pItem = dynamic_cast< CEconEntity* >( pEntity );
if ( pItem )
{
pScriptItem = pItem->GetAttributeContainer()->GetItem();
}
}
}
else
{
// Proxy data can be a script created item itself, if we're in a vgui CModelPanel
pScriptItem = dynamic_cast< CEconItemView* >( pRend );
}
#ifdef TF_CLIENT_DLL
if ( !pScriptItem )
{
// Might be a throwable
CTFWeaponBaseGrenadeProj *pProjectile = dynamic_cast< CTFWeaponBaseGrenadeProj* >( pEntity );
if ( pProjectile )
{
CEconEntity *pItem = dynamic_cast< CEconEntity* >( pProjectile->GetLauncher() );
if ( pItem )
{
pScriptItem = pItem->GetAttributeContainer()->GetItem();
}
}
}
if ( pScriptItem && pScriptItem->IsValid() )
{
const bool bAltColor = pEntity && pEntity->GetTeam() > 0
? pEntity->GetTeam()->GetTeamNumber() == TF_TEAM_BLUE
: pScriptItem->GetFlags() & kEconItemFlagClient_ForceBlueTeam
? true
: false;
int iModifiedRGB = pScriptItem->GetModifiedRGBValue( bAltColor );
if ( iModifiedRGB )
{
// The attrib returns a packed RGB with values between 0 & 255 packed into the bottom 3 bytes.
Color clr = Color( ((iModifiedRGB & 0xFF0000) >> 16), ((iModifiedRGB & 0xFF00) >> 8), (iModifiedRGB & 0xFF) );
vResult.x = clamp( clr.r() * (1.f / 255.0f), 0.f, 1.0f );
vResult.y = clamp( clr.g() * (1.f / 255.0f), 0.f, 1.0f );
vResult.z = clamp( clr.b() * (1.f / 255.0f), 0.f, 1.0f );
}
}
#endif // TF_CLIENT_DLL
}
m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z );
}
};
EXPOSE_INTERFACE( CProxyItemTintColor, IMaterialProxy, "ItemTintColor" IMATERIAL_PROXY_INTERFACE_VERSION );
//============================================================================================================================
extern ConVar r_propsmaxdist;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
C_EconWearableGib::C_EconWearableGib()
{
m_fDeathTime = -1;
m_iHealth = 0;
m_bParented = false;
m_bDelayedInit = false;
}
C_EconWearableGib::~C_EconWearableGib()
{
PhysCleanupFrictionSounds( this );
VPhysicsDestroyObject();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_EconWearableGib::Initialize( bool bWillBeParented )
{
m_bParented = bWillBeParented;
return InitializeAsClientEntity( STRING( GetModelName() ), RENDER_GROUP_OPAQUE_ENTITY );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CStudioHdr* C_EconWearableGib::OnNewModel()
{
CStudioHdr* pCStudioHdr = BaseClass::OnNewModel();
if ( m_bDelayedInit && !IsDynamicModelLoading() )
{
FinishModelInitialization();
}
return pCStudioHdr;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_EconWearableGib::SpawnClientEntity( void )
{
if ( !IsDynamicModelLoading() )
{
FinishModelInitialization();
}
else
{
m_bDelayedInit = true;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_EconWearableGib::FinishModelInitialization( void )
{
UpdateThinkState();
const model_t *mod = GetModel();
if ( mod )
{
Vector mins, maxs;
modelinfo->GetModelBounds( mod, mins, maxs );
SetCollisionBounds( mins, maxs );
}
if ( !m_bParented )
{
// Create the object in the physics system
solid_t tmpSolid;
if ( !PhysModelParseSolid( tmpSolid, this, GetModelIndex() ) )
{
DevMsg("C_EconWearableGib::FinishModelInitialization: PhysModelParseSolid failed for entity %i.\n", GetModelIndex() );
return false;
}
else
{
m_pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, 0, false, &tmpSolid );
if ( !m_pPhysicsObject )
{
// failed to create a physics object
DevMsg(" C_EconWearableGib::FinishModelInitialization: VPhysicsInitNormal() failed for %s.\n", STRING(GetModelName()) );
return false;
}
}
}
Spawn();
if ( m_fadeMinDist < 0 )
{
// start fading out at 75% of r_propsmaxdist
m_fadeMaxDist = r_propsmaxdist.GetFloat();
m_fadeMinDist = r_propsmaxdist.GetFloat() * 0.75f;
}
SetCollisionGroup( COLLISION_GROUP_DEBRIS );
UpdatePartitionListEntry();
CollisionProp()->UpdatePartition();
SetBlocksLOS( false ); // this should be a small object
// Set up shadows; do it here so that objects can change shadowcasting state
CreateShadow();
UpdateVisibility();
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_EconWearableGib::Spawn()
{
BaseClass::Spawn();
m_takedamage = DAMAGE_EVENTS_ONLY;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_EconWearableGib::ValidateEntityAttachedToPlayer( bool &bShouldRetry )
{
bShouldRetry = false;
// Always valid as long as we're not parented to anything
return (GetMoveParent() == NULL);
}
#define WEARABLE_FADEOUT_TIME 1.0f
//-----------------------------------------------------------------------------
// Purpose: Figure out if we need to think or not
//-----------------------------------------------------------------------------
bool C_EconWearableGib::UpdateThinkState( void )
{
if ( m_fDeathTime > 0 )
{
// If we're in the active fadeout portion, think rapidly. Otherwise, wait for that time.
if ( (m_fDeathTime - gpGlobals->curtime) > WEARABLE_FADEOUT_TIME )
{
SetNextClientThink( m_fDeathTime - WEARABLE_FADEOUT_TIME );
}
else
{
SetNextClientThink( CLIENT_THINK_ALWAYS );
}
return true;
}
SetNextClientThink( CLIENT_THINK_NEVER );
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_EconWearableGib::ClientThink( void )
{
if ( (m_fDeathTime > 0) && ((m_fDeathTime - gpGlobals->curtime) <= WEARABLE_FADEOUT_TIME) )
{
if ( m_fDeathTime <= gpGlobals->curtime )
{
Release(); // Die
return;
}
// fade out
float alpha = (m_fDeathTime - gpGlobals->curtime) / WEARABLE_FADEOUT_TIME;
SetRenderMode( kRenderTransTexture );
SetRenderColorA( alpha * 256 );
}
UpdateThinkState();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_EconWearableGib::StartFadeOut( float fDelay )
{
m_fDeathTime = gpGlobals->curtime + fDelay + WEARABLE_FADEOUT_TIME;
UpdateThinkState();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_EconWearableGib::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName )
{
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if( !pPhysicsObject )
return;
Vector dir = pTrace->endpos - pTrace->startpos;
int iDamage = 0;
if ( iDamageType & DMG_BLAST )
{
iDamage = VectorLength( dir );
dir *= 500; // adjust impact strenght
// apply force at object mass center
pPhysicsObject->ApplyForceCenter( dir );
}
else
{
Vector hitpos;
VectorMA( pTrace->startpos, pTrace->fraction, dir, hitpos );
VectorNormalize( dir );
// guess avg damage
if ( iDamageType == DMG_BULLET )
{
iDamage = 30;
}
else
{
iDamage = 50;
}
dir *= 4000; // adjust impact strenght
// apply force where we hit it
pPhysicsObject->ApplyForceOffset( dir, hitpos );
}
}
#if 0
#ifdef _DEBUG
#include "econ_item_system.h"
static CUtlVector< const char * > s_possibleModels;
static CUtlVector< const GameItemDefinition_t * > s_possibleDefinitions;
void Dbg_TestDynamicWearableGibs( void )
{
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
if ( !pLocalPlayer )
return;
C_EconWearableGib *pEntity = new C_EconWearableGib();
if ( !pEntity )
return;
Vector forward;
pLocalPlayer->EyeVectors( &forward );
trace_t tr;
UTIL_TraceLine( pLocalPlayer->EyePosition(), pLocalPlayer->EyePosition() + (forward * 256), MASK_NPCSOLID, pLocalPlayer, COLLISION_GROUP_NONE, &tr );
Vector position = tr.endpos;
if ( s_possibleModels.Count() == 0 )
{
FOR_EACH_MAP( ItemSystem()->GetItemSchema()->GetItemDefinitionMap(), nDefn )
{
const GameItemDefinition_t *pDefn = dynamic_cast<GameItemDefinition_t *>( ItemSystem()->GetItemSchema()->GetItemDefinitionMap()[nDefn] );
if ( !pDefn )
continue;
const char *pszModel = pDefn->GetPlayerDisplayModel( 0 );
if ( pszModel && pszModel[0] && pszModel[0] != '?' && pDefn->BLoadOnDemand() && pDefn->GetDropType() == ITEM_DROP_TYPE_DROP )
{
s_possibleModels.AddToTail( pszModel );
s_possibleDefinitions.AddToTail( pDefn );
}
}
}
Assert( s_possibleModels.Count() );
int spawnIndex = random->RandomInt( 0, s_possibleModels.Count() - 1 );
const char *pszModelName = s_possibleModels[ spawnIndex ];
const GameItemDefinition_t *pDefn = s_possibleDefinitions[ spawnIndex ];
Msg( "Spawning: %s\n", pszModelName );
pEntity->SetModelName( AllocPooledString( pszModelName ) );
pEntity->SetAbsOrigin( position );
pEntity->SetAbsAngles( vec3_angle );
pEntity->SetOwnerEntity( pLocalPlayer );
pEntity->ChangeTeam( pLocalPlayer->GetTeamNumber() ); // our gibs will match our team; this will probably not be used for anything besides team coloring
// Copy the script created item data over
pEntity->GetAttributeContainer()->GetItem()->Init( pDefn->GetDefinitionIndex(), pDefn->GetQuality(), pDefn->GetMinLevel(), true );
if ( !pEntity->Initialize( false ) )
{
pEntity->Release();
return;
}
pEntity->StartFadeOut( 15.0f );
return;
IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
if ( !pPhysicsObject )
{
pEntity->Release();
return;
}
// randomize velocity by 5%
Vector rndVel = Vector(0,0,100);
pPhysicsObject->AddVelocity( &rndVel, &vec3_origin );
}
static ConCommand dbg_testdynamicwearablegib( "dbg_testdynamicwearablegib", Dbg_TestDynamicWearableGibs, "", FCVAR_CHEAT );
#endif // _DEBUG
#endif
#endif // client only