hl2sdk/game/shared/tf/tf_weapon_throwable.cpp
2025-02-19 18:36:16 -05:00

726 lines
22 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "tf_weapon_throwable.h"
#include "tf_gamerules.h"
#include "in_buttons.h"
#include "basetypes.h"
#include "tf_weaponbase_gun.h"
#include "effect_dispatch_data.h"
// Client specific.
#ifdef CLIENT_DLL
#include "c_tf_player.h"
// Server specific.
#else
#include "tf_player.h"
#include "tf_fx.h"
#include "te_effect_dispatch.h"
#include "bone_setup.h"
#include "tf_target_dummy.h"
#endif
// Base
// Launcher
IMPLEMENT_NETWORKCLASS_ALIASED( TFThrowable, DT_TFWeaponThrowable )
BEGIN_NETWORK_TABLE( CTFThrowable, DT_TFWeaponThrowable )
#ifdef CLIENT_DLL
RecvPropFloat( RECVINFO( m_flChargeBeginTime ) ),
#else
SendPropFloat( SENDINFO( m_flChargeBeginTime ) ),
#endif
END_NETWORK_TABLE()
BEGIN_PREDICTION_DATA( CTFThrowable )
END_PREDICTION_DATA()
//LINK_ENTITY_TO_CLASS( tf_weapon_throwable, CTFThrowable );
//PRECACHE_WEAPON_REGISTER( tf_weapon_throwable );
// Projectile
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Throwable, DT_TFProjectile_Throwable )
BEGIN_NETWORK_TABLE( CTFProjectile_Throwable, DT_TFProjectile_Throwable )
END_NETWORK_TABLE()
LINK_ENTITY_TO_CLASS( tf_projectile_throwable, CTFProjectile_Throwable );
PRECACHE_WEAPON_REGISTER( tf_projectile_throwable );
// Projectile Repel
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_ThrowableRepel, DT_TFProjectile_ThrowableRepel )
BEGIN_NETWORK_TABLE( CTFProjectile_ThrowableRepel, DT_TFProjectile_ThrowableRepel )
END_NETWORK_TABLE()
LINK_ENTITY_TO_CLASS( tf_projectile_throwable_repel, CTFProjectile_ThrowableRepel );
PRECACHE_WEAPON_REGISTER( tf_projectile_throwable_repel );
// Projectile Brick
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_ThrowableBrick, DT_TFProjectile_ThrowableBrick )
BEGIN_NETWORK_TABLE( CTFProjectile_ThrowableBrick, DT_TFProjectile_ThrowableBrick )
END_NETWORK_TABLE()
LINK_ENTITY_TO_CLASS( tf_projectile_throwable_brick, CTFProjectile_ThrowableBrick );
PRECACHE_WEAPON_REGISTER( tf_projectile_throwable_brick );
// Projectile Bread Monster
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_ThrowableBreadMonster, DT_TFProjectile_ThrowableBreadMonster )
BEGIN_NETWORK_TABLE( CTFProjectile_ThrowableBreadMonster, DT_TFProjectile_ThrowableBreadMonster )
END_NETWORK_TABLE()
LINK_ENTITY_TO_CLASS( tf_projectile_throwable_breadmonster, CTFProjectile_ThrowableBreadMonster );
PRECACHE_WEAPON_REGISTER( tf_projectile_throwable_breadmonster );
#define TF_GRENADE_TIMER "Weapon_Grenade.Timer"
#define TF_GRENADE_CHARGE "Weapon_LooseCannon.Charge"
//****************************************************************************
// Throwable Weapon
//****************************************************************************
CTFThrowable::CTFThrowable( void )
{
m_flChargeBeginTime = -1.0f;
}
//-----------------------------------------------------------------------------
void CTFThrowable::Precache()
{
BaseClass::Precache();
PrecacheModel( g_pszArrowModels[MODEL_BREAD_MONSTER] );
PrecacheModel( g_pszArrowModels[MODEL_THROWING_KNIFE] );
PrecacheScriptSound( TF_GRENADE_CHARGE );
PrecacheScriptSound( "Weapon_bm_throwable.throw" );
PrecacheScriptSound( "Weapon_bm_throwable.smash" );
PrecacheParticleSystem( "grenade_smoke_cycle" );
PrecacheParticleSystem( "blood_bread_biting" );
}
//-----------------------------------------------------------------------------
float CTFThrowable::InternalGetEffectBarRechargeTime( void )
{
float flRechargeTime = 0;
CALL_ATTRIB_HOOK_FLOAT( flRechargeTime, throwable_recharge_time );
if ( flRechargeTime )
return flRechargeTime;
return 10.0f; // default
}
//-----------------------------------------------------------------------------
float CTFThrowable::GetDetonationTime()
{
float flDetonationTime = 0;
CALL_ATTRIB_HOOK_FLOAT( flDetonationTime, throwable_detonation_time );
if ( flDetonationTime )
return flDetonationTime;
return 5.0f; // default
}
//-----------------------------------------------------------------------------
void CTFThrowable::PrimaryAttack( void )
{
if ( !CanCharge() )
{
// Fire
BaseClass::PrimaryAttack();
return;
}
if ( m_flChargeBeginTime > 0 )
return;
// Do all the Checks and start a charged (primed) attack
CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
if ( !pPlayer )
return;
// Check for ammunition.
if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) < 1 )
return;
// Are we capable of firing again?
if ( m_flNextPrimaryAttack > gpGlobals->curtime )
return;
if ( pPlayer->GetWaterLevel() == WL_Eyes )
return;
if ( !CanAttack() )
return;
if ( m_flChargeBeginTime <= 0 )
{
// Set the weapon mode.
m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
SendWeaponAnim( ACT_VM_PULLBACK ); // TODO : Anim!
#ifdef GAME_DLL
// save that we had the attack button down
m_flChargeBeginTime = gpGlobals->curtime;
#endif // GAME_LL
#ifdef CLIENT_DLL
if ( pPlayer == C_BasePlayer::GetLocalPlayer() )
{
int iCanBeCharged = 0;
CALL_ATTRIB_HOOK_INT( iCanBeCharged, is_throwable_chargeable );
if ( iCanBeCharged )
{
EmitSound( TF_GRENADE_CHARGE );
}
else
{
EmitSound( TF_GRENADE_TIMER );
}
}
#endif // CLIENT_DLL
}
}
//-----------------------------------------------------------------------------
void CTFThrowable::ItemPostFrame( void )
{
// Get the player owning the weapon.
CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
if ( !pPlayer )
return;
if ( m_flChargeBeginTime > 0.f && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) > 0 )
{
bool bFiredWeapon = false;
// If we're not holding down the attack button, launch our grenade
if ( !( pPlayer->m_nButtons & IN_ATTACK ) )
{
FireProjectile( pPlayer );
bFiredWeapon = true;
}
// Misfire
else if ( m_flChargeBeginTime + GetDetonationTime() < gpGlobals->curtime )
{
CTFProjectile_Throwable * pThrowable = dynamic_cast<CTFProjectile_Throwable*>( FireProjectile( pPlayer ) );
if ( pThrowable )
{
#ifdef GAME_DLL
pThrowable->Misfire();
#endif // GAME_DLL
}
bFiredWeapon = true;
}
if ( bFiredWeapon )
{
SendWeaponAnim( ACT_VM_PRIMARYATTACK );
pPlayer->SetAnimation( PLAYER_ATTACK1 );
#ifdef GAME_DLL
m_flChargeBeginTime = -1.0f; // reset
#endif // GAME_DLL
// Set next attack times.
float flFireDelay = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay;
m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay;
SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() );
#ifdef CLIENT_DLL
int iCanBeCharged = 0;
CALL_ATTRIB_HOOK_INT( iCanBeCharged, is_throwable_chargeable );
if ( iCanBeCharged )
{
StopSound( TF_GRENADE_CHARGE );
}
#endif // CLIENT_DLL
}
}
BaseClass::ItemPostFrame();
}
// ITFChargeUpWeapon
//-----------------------------------------------------------------------------
// Primable is for timed explosions
// Charagable is for things like distance or power increases
// Can't really have both but can have neither
bool CTFThrowable::CanCharge()
{
int iCanBePrimed = 0;
CALL_ATTRIB_HOOK_INT( iCanBePrimed, is_throwable_primable );
int iCanBeCharged = 0;
CALL_ATTRIB_HOOK_INT( iCanBeCharged, is_throwable_chargeable );
return iCanBeCharged || iCanBePrimed ;
}
//-----------------------------------------------------------------------------
float CTFThrowable::GetChargeBeginTime( void )
{
float flDetonateTimeLength = GetDetonationTime();
// float flModDetonateTimeLength = 0;
int iCanBePrimed = 0;
CALL_ATTRIB_HOOK_INT( iCanBePrimed, is_throwable_primable );
// Use reverse logic for primable grenades (Counts down to boom)
// Full charge since we haven't fired
if ( iCanBePrimed )
{
if ( m_flChargeBeginTime < 0 )
{
return gpGlobals->curtime - flDetonateTimeLength;
}
return gpGlobals->curtime - Clamp( m_flChargeBeginTime + flDetonateTimeLength - gpGlobals->curtime, 0.f, flDetonateTimeLength );
}
return m_flChargeBeginTime;
}
//-----------------------------------------------------------------------------
float CTFThrowable::GetChargeMaxTime( void )
{
return GetDetonationTime();
}
//-----------------------------------------------------------------------------
CBaseEntity *CTFThrowable::FireJar( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
return FireProjectileInternal();
#endif
return NULL;
}
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
void CTFThrowable::TossJarThink( void )
{
FireProjectileInternal();
}
//-----------------------------------------------------------------------------
CTFProjectile_Throwable *CTFThrowable::FireProjectileInternal( void )
{
CTFPlayer *pPlayer = GetTFPlayerOwner();
if ( !pPlayer )
return NULL;
CAttribute_String attrProjectileEntityName;
GetProjectileEntityName( &attrProjectileEntityName );
if ( !attrProjectileEntityName.has_value() )
return NULL;
Vector vecForward, vecRight, vecUp;
AngleVectors( pPlayer->EyeAngles(), &vecForward, &vecRight, &vecUp );
float fRight = 8.f;
if ( IsViewModelFlipped() )
{
fRight *= -1;
}
Vector vecSrc = pPlayer->Weapon_ShootPosition();
// Make spell toss position at the hand
vecSrc = vecSrc + ( vecUp * -9.0f ) + ( vecRight * 7.0f ) + ( vecForward * 3.0f );
trace_t trace;
Vector vecEye = pPlayer->EyePosition();
CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE );
UTIL_TraceHull( vecEye, vecSrc, -Vector( 8, 8, 8 ), Vector( 8, 8, 8 ), MASK_SOLID_BRUSHONLY, &traceFilter, &trace );
// If we started in solid, don't let them fire at all
if ( trace.startsolid )
return NULL;
CalcIsAttackCritical();
// Create the Grenade and Intialize it appropriately
CTFProjectile_Throwable *pGrenade = static_cast<CTFProjectile_Throwable*>( CBaseEntity::CreateNoSpawn( attrProjectileEntityName.value().c_str(), trace.endpos, pPlayer->EyeAngles(), pPlayer ) );
if ( pGrenade )
{
// Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly.
pGrenade->SetPipebombMode();
pGrenade->SetLauncher( this );
pGrenade->SetCritical( IsCurrentAttackACrit() );
DispatchSpawn( pGrenade );
// Calculate a charge percentage
// For now Charge just effects exit velocity
int iCanBeCharged = 0;
float flChargePercent = 0;
float flDetonateTime = GetDetonationTime();
CALL_ATTRIB_HOOK_INT( iCanBeCharged, is_throwable_chargeable );
if ( iCanBeCharged )
{
flChargePercent = RemapVal( gpGlobals->curtime, m_flChargeBeginTime, m_flChargeBeginTime + flDetonateTime, 0.0f, 1.0f );
}
Vector vecVelocity = pGrenade->GetVelocityVector( vecForward, vecRight, vecUp, flChargePercent );
AngularImpulse angVelocity = pGrenade->GetAngularImpulse();
pGrenade->InitGrenade( vecVelocity, angVelocity, pPlayer, GetTFWpnData() );
pGrenade->InitThrowable( flChargePercent );
pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );
if ( flDetonateTime > 0 )
{
// Check if this has been primed
int iCanBePrimed = 0;
CALL_ATTRIB_HOOK_INT( iCanBePrimed, is_throwable_primable );
if ( m_flChargeBeginTime > 0 && iCanBePrimed > 0 )
{
flDetonateTime = ( m_flChargeBeginTime + flDetonateTime - gpGlobals->curtime );
}
pGrenade->SetDetonateTimerLength( flDetonateTime );
}
pGrenade->m_flFullDamage = 0;
if ( pGrenade->GetThrowSoundEffect() )
{
pGrenade->EmitSound( pGrenade->GetThrowSoundEffect() );
}
}
StartEffectBarRegen();
return pGrenade;
}
#endif // GAME_DLL
//----------------------------------------------------------------------------------------------------------------------------------------------------------
// Throwable Projectile
//----------------------------------------------------------------------------------------------------------------------------------------------------------
#ifdef GAME_DLL
CTFProjectile_Throwable::CTFProjectile_Throwable( void )
{
m_flChargePercent = 0;
m_bHit = false;
}
//----------------------------------------------------------------------------------------------------------------------------------------------------------
// Get Initial Velocity
Vector CTFProjectile_Throwable::GetVelocityVector( const Vector &vecForward, const Vector &vecRight, const Vector &vecUp, float flCharge )
{
// Scale the projectile speed up to a maximum of 3000?
float flSpeed = RemapVal( flCharge, 0, 1.0f, GetProjectileSpeed(), GetProjectileMaxSpeed() );
return ( ( flSpeed * vecForward ) +
( ( random->RandomFloat( -10.0f, 10.0f ) + 200.0f ) * vecUp ) +
( random->RandomFloat( -10.0f, 10.0f ) * vecRight ) );
}
//----------------------------------------------------------------------------------------------------------------------------------------------------------
void CTFProjectile_Throwable::OnHit( CBaseEntity *pOther )
{
if ( m_bHit )
return;
if ( ExplodesOnHit() )
{
Explode();
}
m_bHit = true;
}
//-----------------------------------------------------------------------------
void CTFProjectile_Throwable::Explode()
{
trace_t tr;
Vector vecSpot;// trace starts here!
SetThink( NULL );
vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );
UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -32 ), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, & tr);
Explode( &tr, GetDamageType() );
}
//-----------------------------------------------------------------------------
void CTFProjectile_Throwable::Explode( trace_t *pTrace, int bitsDamageType )
{
if ( GetThrower() )
{
InitialExplodeEffects( NULL, pTrace );
// Particle
const char* pszExplodeEffect = GetExplodeEffectParticle();
if ( pszExplodeEffect && pszExplodeEffect[0] != '\0' )
{
CPVSFilter filter( GetAbsOrigin() );
TE_TFParticleEffect( filter, 0.0, pszExplodeEffect, GetAbsOrigin(), vec3_angle );
}
// Sounds
const char* pszSoundEffect = GetExplodeEffectSound();
if ( pszSoundEffect && pszSoundEffect[0] != '\0' )
{
EmitSound( pszSoundEffect );
}
}
SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
SetTouch( NULL );
AddEffects( EF_NODRAW );
SetAbsVelocity( vec3_origin );
}
//-----------------------------------------------------------------------------
// THROWABLE REPEL
//-----------------------------------------------------------------------------
void CTFProjectile_ThrowableRepel::OnHit( CBaseEntity *pOther )
{
if ( m_bHit )
return;
CTFPlayer *pPlayer = dynamic_cast< CTFPlayer*>( pOther );
if ( pPlayer && !pPlayer->InSameTeam( GetThrower() ) )
{
if ( pPlayer->m_Shared.IsImmuneToPushback() )
return;
CTraceFilterIgnoreTeammates tracefilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
trace_t trace;
UTIL_TraceLine( GetAbsOrigin(), pPlayer->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &tracefilter, &trace );
// Apply AirBlast Force
Vector vecToTarget;
vecToTarget = pPlayer->GetAbsOrigin() - this->GetAbsOrigin();
vecToTarget.z = 0;
VectorNormalize( vecToTarget );
float flForce = 300.0f * m_flChargePercent + 350.0f;
pPlayer->ApplyGenericPushbackImpulse( vecToTarget * flForce + Vector( 0, 0, flForce ), ToTFPlayer( GetThrower() ) );
pPlayer->ApplyPunchImpulseX( RandomInt( -50, -30 ) );
// Apply Damage to Victim
CTakeDamageInfo info;
info.SetAttacker( GetThrower() );
info.SetInflictor( this );
info.SetWeapon( GetLauncher() );
info.SetDamage( GetDamage() );
info.SetDamageCustom( GetCustomDamageType() );
info.SetDamagePosition( this->GetAbsOrigin() );
info.SetDamageType( DMG_CLUB | DMG_PREVENT_PHYSICS_FORCE );
//Vector dir;
//AngleVectors( GetAbsAngles(), &dir );
pPlayer->DispatchTraceAttack( info, vecToTarget, &trace );
ApplyMultiDamage();
}
BaseClass::OnHit( pOther );
}
//-----------------------------------------------------------------------------
// THROWABLE BRICK
//-----------------------------------------------------------------------------
void CTFProjectile_ThrowableBrick::OnHit( CBaseEntity *pOther )
{
if ( m_bHit )
return;
CTFPlayer *pPlayer = dynamic_cast< CTFPlayer*>( pOther );
if ( pPlayer && !pPlayer->InSameTeam( GetThrower() ) )
{
CTraceFilterIgnoreTeammates tracefilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
trace_t trace;
UTIL_TraceLine( GetAbsOrigin(), pPlayer->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &tracefilter, &trace );
Vector vecToTarget;
vecToTarget = pPlayer->WorldSpaceCenter() - this->WorldSpaceCenter();
VectorNormalize( vecToTarget );
// Apply Damage to Victim
CTakeDamageInfo info;
info.SetAttacker( GetThrower() );
info.SetInflictor( this );
info.SetWeapon( GetLauncher() );
info.SetDamage( GetDamage() );
info.SetDamageCustom( GetCustomDamageType() );
info.SetDamagePosition( GetAbsOrigin() );
info.SetDamageType( DMG_CLUB );
pPlayer->DispatchTraceAttack( info, vecToTarget, &trace );
pPlayer->ApplyPunchImpulseX( RandomInt( 15, 20 ) );
ApplyMultiDamage();
}
BaseClass::OnHit( pOther );
}
//-----------------------------------------------------------------------------
// THROWABLE BREADMONSTER
//-----------------------------------------------------------------------------
void CTFProjectile_ThrowableBreadMonster::OnHit( CBaseEntity *pOther )
{
if ( m_bHit )
return;
CTFPlayer *pVictim = dynamic_cast< CTFPlayer*>( pOther );
CTFPlayer *pOwner = dynamic_cast< CTFPlayer*>( GetThrower() );
if ( pVictim && pOwner && !pVictim->InSameTeam( pOwner ) )
{
m_bHit = true;
CTraceFilterIgnoreTeammates tracefilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
trace_t trace;
Vector vEndPos = pVictim->WorldSpaceCenter();
vEndPos.z = WorldSpaceCenter().z + 1.0f;
UTIL_TraceLine( WorldSpaceCenter(), vEndPos, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &tracefilter, &trace );
Vector vecToTarget;
vecToTarget = pVictim->WorldSpaceCenter() - this->WorldSpaceCenter();
VectorNormalize( vecToTarget );
// Apply Damage to Victim
CTakeDamageInfo info;
info.SetAttacker( GetThrower() );
info.SetInflictor( this );
info.SetWeapon( GetLauncher() );
info.SetDamage( GetDamage() );
info.SetDamageCustom( GetCustomDamageType() );
info.SetDamagePosition( GetAbsOrigin() );
int iDamageType = DMG_CLUB;
if ( IsCritical() )
{
iDamageType |= DMG_CRITICAL;
}
info.SetDamageType( iDamageType );
pVictim->DispatchTraceAttack( info, vecToTarget, &trace );
pVictim->ApplyPunchImpulseX( RandomInt( 15, 20 ) );
pVictim->m_Shared.MakeBleed( pOwner, dynamic_cast< CTFWeaponBase * >( GetLauncher() ), 5.0f, 1.0f );
ApplyMultiDamage();
// Bread Particle
CPVSFilter filter( vEndPos );
TE_TFParticleEffect( filter, 0.0, "blood_bread_biting", vEndPos, vec3_angle );
// Attach Breadmonster to Victim
CreateStickyAttachmentToTarget( pOwner, pVictim, &trace );
BaseClass::Explode();
return;
}
else // its a dud
{
BaseClass::Explode();
return;
}
BaseClass::OnHit( pOther );
}
//-----------------------------------------------------------------------------
void CTFProjectile_ThrowableBreadMonster::Detonate()
{
SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
SetTouch( NULL );
AddEffects( EF_NODRAW );
SetAbsVelocity( vec3_origin );
}
//-----------------------------------------------------------------------------
void CTFProjectile_ThrowableBreadMonster::Explode( trace_t *pTrace, int bitsDamageType )
{
if ( !m_bHit )
{
// TODO, Spawn Debris / Flopping BreadInstead
trace_t tr;
Vector velDir = m_vCollisionVelocity;
VectorNormalize( velDir );
Vector vecSpot = GetAbsOrigin() - velDir * 32;
UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &tr );
if ( tr.fraction < 1.0 && tr.surface.flags & SURF_SKY )
{
// We hit the skybox, go away soon.
return;
}
// Create a breadmonster in the world
CEffectData data;
data.m_vOrigin = tr.endpos;
data.m_vNormal = velDir;
data.m_nEntIndex = 0;
data.m_nAttachmentIndex = 0;
data.m_nMaterial = 0;
data.m_fFlags = TF_PROJECTILE_BREAD_MONSTER;
data.m_nColor = ( GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0;
DispatchEffect( "TFBoltImpact", data );
}
BaseClass::Explode( pTrace, bitsDamageType );
}
#endif // GAME_DLL
//
//#ifdef CLIENT_DLL
//
//static CUtlMap< const char*, CUtlString > s_TeamParticleMap;
//static bool s_TeamParticleMapInited = false;
//
////-----------------------------------------------------------------------------
//const char *CTFProjectile_Throwable::GetTrailParticleName( void )
//{
// // Check for Particles
// int iDynamicParticleEffect = 0;
// CALL_ATTRIB_HOOK_INT_ON_OTHER( GetLauncher(), iDynamicParticleEffect, set_attached_particle );
// if ( iDynamicParticleEffect > 0 )
// {
// // Init Map Once
// if ( !s_TeamParticleMapInited )
// {
// SetDefLessFunc( s_TeamParticleMap );
// s_TeamParticleMapInited = true;
// }
//
// attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iDynamicParticleEffect );
// if ( pParticleSystem )
// {
// // TF Team Color Particles
// const char * pName = pParticleSystem->pszSystemName;
// if ( GetTeamNumber() == TF_TEAM_BLUE && V_stristr( pName, "_teamcolor_red" ))
// {
// int index = s_TeamParticleMap.Find( pName );
// if ( !s_TeamParticleMap.IsValidIndex( index ) )
// {
// char pBlue[256];
// V_StrSubst( pName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
// CUtlString pBlueString( pBlue );
// index = s_TeamParticleMap.Insert( pName, pBlueString );
// }
// return s_TeamParticleMap[index].String();
// }
// else if ( GetTeamNumber() == TF_TEAM_RED && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_blue" ))
// {
// // Guard against accidentally giving out the blue team color (support tool)
// int index = s_TeamParticleMap.Find( pName );
// if ( !s_TeamParticleMap.IsValidIndex( index ) )
// {
// char pRed[256];
// V_StrSubst( pName, "_teamcolor_blue", "_teamcolor_red", pRed, 256 );
// CUtlString pRedString( pRed );
// index = s_TeamParticleMap.Insert( pName, pRedString );
// }
// return s_TeamParticleMap[index].String();
// }
//
// return pName;
// }
// }
//
// if ( GetTeamNumber() == TF_TEAM_BLUE )
// {
// return "trail_basic_blue";
// }
// else
// {
// return "trail_basic_red";
// }
//}
//
//#endif // CLIENT_DLL
//----------------------------------------------------------------------------------------------------------------------------------------------------------