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.
450 lines
14 KiB
450 lines
14 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//===========================================================================// |
|
#include "cbase.h" |
|
#include "decals.h" |
|
#include "materialsystem/imaterialvar.h" |
|
#include "IEffects.h" |
|
#include "fx.h" |
|
#include "fx_impact.h" |
|
#include "view.h" |
|
#ifdef TF_CLIENT_DLL |
|
#include "cdll_util.h" |
|
#endif |
|
#include "engine/IStaticPropMgr.h" |
|
#include "c_impact_effects.h" |
|
#include "tier0/vprof.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
static ConVar r_drawflecks( "r_drawflecks", "1" ); |
|
extern ConVar r_drawmodeldecals; |
|
|
|
ImpactSoundRouteFn g_pImpactSoundRouteFn = NULL; |
|
|
|
//========================================================================================================================== |
|
// RAGDOLL ENUMERATOR |
|
//========================================================================================================================== |
|
CRagdollEnumerator::CRagdollEnumerator( Ray_t& shot, int iDamageType ) |
|
{ |
|
m_rayShot = shot; |
|
m_iDamageType = iDamageType; |
|
m_bHit = false; |
|
} |
|
|
|
IterationRetval_t CRagdollEnumerator::EnumElement( IHandleEntity *pHandleEntity ) |
|
{ |
|
C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() ); |
|
if ( pEnt == NULL ) |
|
return ITERATION_CONTINUE; |
|
|
|
C_BaseAnimating *pModel = static_cast< C_BaseAnimating * >( pEnt ); |
|
|
|
// If the ragdoll was created on this tick, then the forces were already applied on the server |
|
if ( pModel == NULL || WasRagdollCreatedOnCurrentTick( pEnt ) ) |
|
return ITERATION_CONTINUE; |
|
|
|
IPhysicsObject *pPhysicsObject = pModel->VPhysicsGetObject(); |
|
if ( pPhysicsObject == NULL ) |
|
return ITERATION_CONTINUE; |
|
|
|
trace_t tr; |
|
enginetrace->ClipRayToEntity( m_rayShot, MASK_SHOT, pModel, &tr ); |
|
|
|
if ( tr.fraction < 1.0 ) |
|
{ |
|
pModel->ImpactTrace( &tr, m_iDamageType, NULL ); |
|
m_bHit = true; |
|
|
|
//FIXME: Yes? No? |
|
return ITERATION_STOP; |
|
} |
|
|
|
return ITERATION_CONTINUE; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool FX_AffectRagdolls( Vector vecOrigin, Vector vecStart, int iDamageType ) |
|
{ |
|
// don't do this when lots of ragdolls are simulating |
|
if ( s_RagdollLRU.CountRagdolls(true) > 1 ) |
|
return false; |
|
Ray_t shotRay; |
|
shotRay.Init( vecStart, vecOrigin ); |
|
|
|
CRagdollEnumerator ragdollEnum( shotRay, iDamageType ); |
|
partition->EnumerateElementsAlongRay( PARTITION_CLIENT_RESPONSIVE_EDICTS, shotRay, false, &ragdollEnum ); |
|
|
|
return ragdollEnum.Hit(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &data - |
|
//----------------------------------------------------------------------------- |
|
void RagdollImpactCallback( const CEffectData &data ) |
|
{ |
|
FX_AffectRagdolls( data.m_vOrigin, data.m_vStart, data.m_nDamageType ); |
|
} |
|
|
|
DECLARE_CLIENT_EFFECT( "RagdollImpact", RagdollImpactCallback ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool Impact( Vector &vecOrigin, Vector &vecStart, int iMaterial, int iDamageType, int iHitbox, C_BaseEntity *pEntity, trace_t &tr, int nFlags, int maxLODToDecal ) |
|
{ |
|
VPROF( "Impact" ); |
|
|
|
Assert ( pEntity ); |
|
|
|
// Clear out the trace |
|
memset( &tr, 0, sizeof(trace_t)); |
|
tr.fraction = 1.0f; |
|
|
|
// Setup our shot information |
|
Vector shotDir = vecOrigin - vecStart; |
|
float flLength = VectorNormalize( shotDir ); |
|
Vector traceExt; |
|
VectorMA( vecStart, flLength + 8.0f, shotDir, traceExt ); |
|
|
|
// Attempt to hit ragdolls |
|
|
|
bool bHitRagdoll = false; |
|
|
|
if ( !pEntity->IsClientCreated() ) |
|
{ |
|
bHitRagdoll = FX_AffectRagdolls( vecOrigin, vecStart, iDamageType ); |
|
} |
|
|
|
if ( (nFlags & IMPACT_NODECAL) == 0 ) |
|
{ |
|
const char *pchDecalName = GetImpactDecal( pEntity, iMaterial, iDamageType ); |
|
int decalNumber = decalsystem->GetDecalIndexForName( pchDecalName ); |
|
if ( decalNumber == -1 ) |
|
return false; |
|
|
|
bool bSkipDecal = false; |
|
|
|
#ifdef TF_CLIENT_DLL |
|
// Don't show blood decals if we're filtering them out (Pyro Goggles) |
|
if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) || UTIL_IsLowViolence() ) |
|
{ |
|
if ( V_strstr( pchDecalName, "Flesh" ) ) |
|
{ |
|
bSkipDecal = true; |
|
} |
|
} |
|
#endif |
|
|
|
if ( !bSkipDecal ) |
|
{ |
|
if ( (pEntity->entindex() == 0) && (iHitbox != 0) ) |
|
{ |
|
staticpropmgr->AddDecalToStaticProp( vecStart, traceExt, iHitbox - 1, decalNumber, true, tr ); |
|
} |
|
else if ( pEntity ) |
|
{ |
|
// Here we deal with decals on entities. |
|
pEntity->AddDecal( vecStart, traceExt, vecOrigin, iHitbox, decalNumber, true, tr, maxLODToDecal ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Perform the trace ourselves |
|
Ray_t ray; |
|
ray.Init( vecStart, traceExt ); |
|
|
|
if ( (pEntity->entindex() == 0) && (iHitbox != 0) ) |
|
{ |
|
// Special case for world entity with hitbox (that's a static prop) |
|
ICollideable *pCollideable = staticpropmgr->GetStaticPropByIndex( iHitbox - 1 ); |
|
enginetrace->ClipRayToCollideable( ray, MASK_SHOT, pCollideable, &tr ); |
|
} |
|
else |
|
{ |
|
if ( !pEntity ) |
|
return false; |
|
|
|
enginetrace->ClipRayToEntity( ray, MASK_SHOT, pEntity, &tr ); |
|
} |
|
} |
|
|
|
// If we found the surface, emit debris flecks |
|
bool bReportRagdollImpacts = (nFlags & IMPACT_REPORT_RAGDOLL_IMPACTS) != 0; |
|
if ( ( tr.fraction == 1.0f ) || ( bHitRagdoll && !bReportRagdollImpacts ) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
char const *GetImpactDecal( C_BaseEntity *pEntity, int iMaterial, int iDamageType ) |
|
{ |
|
char const *decalName; |
|
if ( !pEntity ) |
|
{ |
|
decalName = "Impact.Concrete"; |
|
} |
|
else |
|
{ |
|
decalName = pEntity->DamageDecal( iDamageType, iMaterial ); |
|
} |
|
|
|
// See if we need to offset the decal for material type |
|
return decalsystem->TranslateDecalForGameMaterial( decalName, iMaterial ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Perform custom effects based on the Decal index |
|
//----------------------------------------------------------------------------- |
|
static ConVar cl_new_impact_effects( "cl_new_impact_effects", "0" ); |
|
|
|
struct ImpactEffect_t |
|
{ |
|
const char *m_pName; |
|
const char *m_pNameNoFlecks; |
|
}; |
|
|
|
static ImpactEffect_t s_pImpactEffect[26] = |
|
{ |
|
{ "impact_antlion", NULL }, // CHAR_TEX_ANTLION |
|
{ NULL, NULL }, // CHAR_TEX_BLOODYFLESH |
|
{ "impact_concrete", "impact_concrete_noflecks" }, // CHAR_TEX_CONCRETE |
|
{ "impact_dirt", NULL }, // CHAR_TEX_DIRT |
|
{ NULL, NULL }, // CHAR_TEX_EGGSHELL |
|
{ NULL, NULL }, // CHAR_TEX_FLESH |
|
{ NULL, NULL }, // CHAR_TEX_GRATE |
|
{ NULL, NULL }, // CHAR_TEX_ALIENFLESH |
|
{ NULL, NULL }, // CHAR_TEX_CLIP |
|
{ NULL, NULL }, // CHAR_TEX_UNUSED |
|
{ NULL, NULL }, // CHAR_TEX_UNUSED |
|
{ NULL, NULL }, // CHAR_TEX_PLASTIC |
|
{ "impact_metal", NULL }, // CHAR_TEX_METAL |
|
{ "impact_dirt", NULL }, // CHAR_TEX_SAND |
|
{ NULL, NULL }, // CHAR_TEX_FOLIAGE |
|
{ "impact_computer", NULL }, // CHAR_TEX_COMPUTER |
|
{ NULL, NULL }, // CHAR_TEX_UNUSED |
|
{ NULL, NULL }, // CHAR_TEX_UNUSED |
|
{ NULL, NULL }, // CHAR_TEX_SLOSH |
|
{ "impact_concrete", "impact_concrete_noflecks" }, // CHAR_TEX_TILE |
|
{ NULL, NULL }, // CHAR_TEX_UNUSED |
|
{ "impact_metal", NULL }, // CHAR_TEX_VENT |
|
{ "impact_wood", "impact_wood_noflecks" }, // CHAR_TEX_WOOD |
|
{ NULL, NULL }, // CHAR_TEX_UNUSED |
|
{ "impact_glass", NULL }, // CHAR_TEX_GLASS |
|
{ "warp_shield_impact", NULL }, // CHAR_TEX_WARPSHIELD |
|
}; |
|
|
|
static void SetImpactControlPoint( CNewParticleEffect *pEffect, int nPoint, const Vector &vecImpactPoint, const Vector &vecForward, C_BaseEntity *pEntity ) |
|
{ |
|
Vector vecImpactY, vecImpactZ; |
|
VectorVectors( vecForward, vecImpactY, vecImpactZ ); |
|
vecImpactY *= -1.0f; |
|
|
|
pEffect->SetControlPoint( nPoint, vecImpactPoint ); |
|
pEffect->SetControlPointOrientation( nPoint, vecForward, vecImpactY, vecImpactZ ); |
|
pEffect->SetControlPointEntity( nPoint, pEntity ); |
|
} |
|
|
|
static void PerformNewCustomEffects( const Vector &vecOrigin, trace_t &tr, const Vector &shotDir, int iMaterial, int iScale, int nFlags ) |
|
{ |
|
bool bNoFlecks = !r_drawflecks.GetBool(); |
|
if ( !bNoFlecks ) |
|
{ |
|
bNoFlecks = ( ( nFlags & FLAGS_CUSTIOM_EFFECTS_NOFLECKS ) != 0 ); |
|
} |
|
|
|
// Compute the impact effect name |
|
const ImpactEffect_t &effect = s_pImpactEffect[ iMaterial - 'A' ]; |
|
const char *pImpactName = effect.m_pName; |
|
if ( bNoFlecks && effect.m_pNameNoFlecks ) |
|
{ |
|
pImpactName = effect.m_pNameNoFlecks; |
|
} |
|
if ( !pImpactName ) |
|
return; |
|
|
|
CSmartPtr<CNewParticleEffect> pEffect = CNewParticleEffect::Create( NULL, pImpactName ); |
|
if ( !pEffect->IsValid() ) |
|
return; |
|
|
|
Vector vecReflect; |
|
float flDot = DotProduct( shotDir, tr.plane.normal ); |
|
VectorMA( shotDir, -2.0f * flDot, tr.plane.normal, vecReflect ); |
|
|
|
Vector vecShotBackward; |
|
VectorMultiply( shotDir, -1.0f, vecShotBackward ); |
|
|
|
Vector vecImpactPoint = ( tr.fraction != 1.0f ) ? tr.endpos : vecOrigin; |
|
Assert( VectorsAreEqual( vecOrigin, tr.endpos, 1e-1 ) ); |
|
|
|
SetImpactControlPoint( pEffect.GetObject(), 0, vecImpactPoint, tr.plane.normal, tr.m_pEnt ); |
|
SetImpactControlPoint( pEffect.GetObject(), 1, vecImpactPoint, vecReflect, tr.m_pEnt ); |
|
SetImpactControlPoint( pEffect.GetObject(), 2, vecImpactPoint, vecShotBackward, tr.m_pEnt ); |
|
pEffect->SetControlPoint( 3, Vector( iScale, iScale, iScale ) ); |
|
if ( pEffect->m_pDef->ReadsControlPoint( 4 ) ) |
|
{ |
|
Vector vecColor; |
|
GetColorForSurface( &tr, &vecColor ); |
|
pEffect->SetControlPoint( 4, vecColor ); |
|
} |
|
} |
|
|
|
void PerformCustomEffects( const Vector &vecOrigin, trace_t &tr, const Vector &shotDir, int iMaterial, int iScale, int nFlags ) |
|
{ |
|
// Throw out the effect if any of these are true |
|
if ( tr.surface.flags & (SURF_SKY|SURF_NODRAW|SURF_HINT|SURF_SKIP) ) |
|
return; |
|
|
|
if ( cl_new_impact_effects.GetInt() ) |
|
{ |
|
PerformNewCustomEffects( vecOrigin, tr, shotDir, iMaterial, iScale, nFlags ); |
|
return; |
|
} |
|
|
|
bool bNoFlecks = !r_drawflecks.GetBool(); |
|
if ( !bNoFlecks ) |
|
{ |
|
bNoFlecks = ( ( nFlags & FLAGS_CUSTIOM_EFFECTS_NOFLECKS ) != 0 ); |
|
} |
|
|
|
// Cement and wood have dust and flecks |
|
if ( ( iMaterial == CHAR_TEX_CONCRETE ) || ( iMaterial == CHAR_TEX_TILE ) ) |
|
{ |
|
FX_DebrisFlecks( vecOrigin, &tr, iMaterial, iScale, bNoFlecks ); |
|
} |
|
else if ( iMaterial == CHAR_TEX_WOOD ) |
|
{ |
|
FX_DebrisFlecks( vecOrigin, &tr, iMaterial, iScale, bNoFlecks ); |
|
} |
|
else if ( ( iMaterial == CHAR_TEX_DIRT ) || ( iMaterial == CHAR_TEX_SAND ) ) |
|
{ |
|
FX_DustImpact( vecOrigin, &tr, iScale ); |
|
} |
|
else if ( iMaterial == CHAR_TEX_ANTLION ) |
|
{ |
|
FX_AntlionImpact( vecOrigin, &tr ); |
|
} |
|
else if ( ( iMaterial == CHAR_TEX_METAL ) || ( iMaterial == CHAR_TEX_VENT ) ) |
|
{ |
|
Vector reflect; |
|
float dot = shotDir.Dot( tr.plane.normal ); |
|
reflect = shotDir + ( tr.plane.normal * ( dot*-2.0f ) ); |
|
|
|
reflect[0] += random->RandomFloat( -0.2f, 0.2f ); |
|
reflect[1] += random->RandomFloat( -0.2f, 0.2f ); |
|
reflect[2] += random->RandomFloat( -0.2f, 0.2f ); |
|
|
|
FX_MetalSpark( vecOrigin, reflect, tr.plane.normal, iScale ); |
|
} |
|
else if ( iMaterial == CHAR_TEX_COMPUTER ) |
|
{ |
|
Vector offset = vecOrigin + ( tr.plane.normal * 1.0f ); |
|
|
|
g_pEffects->Sparks( offset ); |
|
} |
|
else if ( iMaterial == CHAR_TEX_WARPSHIELD ) |
|
{ |
|
QAngle vecAngles; |
|
VectorAngles( -shotDir, vecAngles ); |
|
DispatchParticleEffect( "warp_shield_impact", vecOrigin, vecAngles ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a sound for an impact. If tr contains a valid hit, use that. |
|
// If not, use the passed in origin & surface. |
|
//----------------------------------------------------------------------------- |
|
void PlayImpactSound( CBaseEntity *pEntity, trace_t &tr, Vector &vecServerOrigin, int nServerSurfaceProp ) |
|
{ |
|
VPROF( "PlayImpactSound" ); |
|
surfacedata_t *pdata; |
|
Vector vecOrigin; |
|
|
|
// If the client-side trace hit a different entity than the server, or |
|
// the server didn't specify a surfaceprop, then use the client-side trace |
|
// material if it's valid. |
|
if ( tr.DidHit() && (pEntity != tr.m_pEnt || nServerSurfaceProp == 0) ) |
|
{ |
|
nServerSurfaceProp = tr.surface.surfaceProps; |
|
} |
|
pdata = physprops->GetSurfaceData( nServerSurfaceProp ); |
|
if ( tr.fraction < 1.0 ) |
|
{ |
|
vecOrigin = tr.endpos; |
|
} |
|
else |
|
{ |
|
vecOrigin = vecServerOrigin; |
|
} |
|
|
|
// Now play the esound |
|
if ( pdata->sounds.bulletImpact ) |
|
{ |
|
const char *pbulletImpactSoundName = physprops->GetString( pdata->sounds.bulletImpact ); |
|
|
|
if ( g_pImpactSoundRouteFn ) |
|
{ |
|
g_pImpactSoundRouteFn( pbulletImpactSoundName, vecOrigin ); |
|
} |
|
else |
|
{ |
|
CLocalPlayerFilter filter; |
|
C_BaseEntity::EmitSound( filter, NULL, pbulletImpactSoundName, pdata->soundhandles.bulletImpact, &vecOrigin ); |
|
} |
|
|
|
return; |
|
} |
|
|
|
#ifdef _DEBUG |
|
Msg("***ERROR: PlayImpactSound() on a surface with 0 bulletImpactCount!\n"); |
|
#endif //_DEBUG |
|
} |
|
|
|
|
|
void SetImpactSoundRoute( ImpactSoundRouteFn fn ) |
|
{ |
|
g_pImpactSoundRouteFn = fn; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Pull the impact data out |
|
// Input : &data - |
|
// *vecOrigin - |
|
// *vecAngles - |
|
// *iMaterial - |
|
// *iDamageType - |
|
// *iHitbox - |
|
// *iEntIndex - |
|
//----------------------------------------------------------------------------- |
|
C_BaseEntity *ParseImpactData( const CEffectData &data, Vector *vecOrigin, Vector *vecStart, |
|
Vector *vecShotDir, short &nSurfaceProp, int &iMaterial, int &iDamageType, int &iHitbox ) |
|
{ |
|
C_BaseEntity *pEntity = data.GetEntity( ); |
|
*vecOrigin = data.m_vOrigin; |
|
*vecStart = data.m_vStart; |
|
nSurfaceProp = data.m_nSurfaceProp; |
|
iDamageType = data.m_nDamageType; |
|
iHitbox = data.m_nHitBox; |
|
|
|
*vecShotDir = (*vecOrigin - *vecStart); |
|
VectorNormalize( *vecShotDir ); |
|
|
|
// Get the material from the surfaceprop |
|
surfacedata_t *psurfaceData = physprops->GetSurfaceData( data.m_nSurfaceProp ); |
|
iMaterial = psurfaceData->game.material; |
|
|
|
return pEntity; |
|
} |
|
|
|
|