//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "tf_weaponbase_grenadeproj.h" #include "tf_gamerules.h" // Client specific. #ifdef CLIENT_DLL #include "c_tf_player.h" // Server specific. #else #include "soundent.h" #include "te_effect_dispatch.h" #include "tf_player.h" #include "func_break.h" #include "func_nogrenades.h" #include "Sprite.h" #include "tf_fx.h" #include "halloween/merasmus/merasmus.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //============================================================================= // // TF Grenade projectile tables. // // Server specific. #ifdef GAME_DLL BEGIN_DATADESC( CTFWeaponBaseGrenadeProj ) DEFINE_THINKFUNC( DetonateThink ), END_DATADESC() #ifdef STAGING_ONLY ConVar tf_grenade_show_radius( "tf_grenade_show_radius", "0", FCVAR_CHEAT, "Render radius of grenades" ); ConVar tf_grenade_show_radius_time( "tf_grenade_show_radius_time", "5.0", FCVAR_CHEAT, "Time to show grenade radius" ); #endif // STAGING_ONLY extern void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ); extern void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ); #endif IMPLEMENT_NETWORKCLASS_ALIASED( TFWeaponBaseGrenadeProj, DT_TFWeaponBaseGrenadeProj ) LINK_ENTITY_TO_CLASS( tf_weaponbase_grenade_proj, CTFWeaponBaseGrenadeProj ); PRECACHE_REGISTER( tf_weaponbase_grenade_proj ); BEGIN_NETWORK_TABLE( CTFWeaponBaseGrenadeProj, DT_TFWeaponBaseGrenadeProj ) #ifdef CLIENT_DLL RecvPropVector( RECVINFO( m_vInitialVelocity ) ), RecvPropBool( RECVINFO( m_bCritical ) ), RecvPropInt( RECVINFO( m_iDeflected ) ), RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ), RecvPropQAngles( RECVINFO_NAME( m_angNetworkAngles, m_angRotation ) ), RecvPropEHandle( RECVINFO( m_hDeflectOwner )), #else SendPropVector( SENDINFO( m_vInitialVelocity ), 20 /*nbits*/, 0 /*flags*/, -3000 /*low value*/, 3000 /*high value*/ ), SendPropBool( SENDINFO( m_bCritical ) ), SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ), SendPropExclude( "DT_BaseEntity", "m_angRotation" ), SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_COORD_MP_INTEGRAL|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ), SendPropQAngles (SENDINFO(m_angRotation), 6, SPROP_CHANGES_OFTEN, SendProxy_Angles ), SendPropInt( SENDINFO( m_iDeflected ), 4, SPROP_UNSIGNED ), SendPropEHandle(SENDINFO( m_hDeflectOwner )), #endif END_NETWORK_TABLE() //----------------------------------------------------------------------------- // Purpose: Constructor. //----------------------------------------------------------------------------- CTFWeaponBaseGrenadeProj::CTFWeaponBaseGrenadeProj() { #ifndef CLIENT_DLL m_bUseImpactNormal = false; m_vecImpactNormal.Init(); m_iDeflected = 0; m_flDestroyableTime = 0.0f; m_bIsMerasmusGrenade = false; m_iDestroyableHitCount = 0; #endif } //----------------------------------------------------------------------------- // Purpose: Destructor. //----------------------------------------------------------------------------- CTFWeaponBaseGrenadeProj::~CTFWeaponBaseGrenadeProj() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFWeaponBaseGrenadeProj::GetDamageType() { int iDmgType = g_aWeaponDamageTypes[ GetWeaponID() ]; if ( m_bCritical ) { iDmgType |= DMG_CRITICAL; } return iDmgType; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFWeaponBaseGrenadeProj::GetDamageCustom() { return 0; } const float GRENADE_COEFFICIENT_OF_RESTITUTION = 0.2f; //----------------------------------------------------------------------------- // Purpose: Bounce backwards //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::BounceOff( IPhysicsObject *pPhysics ) { if ( !pPhysics ) return; Vector vecVel; pPhysics->GetVelocity( &vecVel, NULL ); vecVel *= -GRENADE_COEFFICIENT_OF_RESTITUTION; pPhysics->SetVelocity( &vecVel, NULL ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFWeaponBaseGrenadeProj::GetDamageRadius() { float flRadius = m_DmgRadius; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_hLauncher, flRadius, mult_explosion_radius ); return flRadius; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::Precache( void ) { BaseClass::Precache(); #ifndef CLIENT_DLL PrecacheModel( NOGRENADE_SPRITE ); PrecacheParticleSystem( "critical_grenade_blue" ); PrecacheParticleSystem( "critical_grenade_red" ); #endif } //============================================================================= // // Client specific functions. // #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::Spawn() { m_flSpawnTime = gpGlobals->curtime; BaseClass::Spawn(); AddFlag( FL_GRENADE ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::OnDataChanged( DataUpdateType_t type ) { BaseClass::OnDataChanged( type ); if ( type == DATA_UPDATE_CREATED ) { // Now stick our initial velocity into the interpolation history CInterpolatedVar< Vector > &interpolator = GetOriginInterpolator(); interpolator.ClearHistory(); float changeTime = GetLastChangeTime( LATCH_SIMULATION_VAR ); // Add a sample 1 second back. Vector vCurOrigin = GetLocalOrigin() - m_vInitialVelocity; interpolator.AddToHead( changeTime - 1.0, &vCurOrigin, false ); // Add the current sample. vCurOrigin = GetLocalOrigin(); interpolator.AddToHead( changeTime, &vCurOrigin, false ); } } //============================================================================= // // Server specific functions. // #else //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFWeaponBaseGrenadeProj *CTFWeaponBaseGrenadeProj::Create( const char *szName, const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo, int iFlags ) { CTFWeaponBaseGrenadeProj *pGrenade = static_cast( CBaseEntity::Create( szName, position, angles, pOwner ) ); if ( pGrenade ) { pGrenade->InitGrenade( velocity, angVelocity, pOwner, weaponInfo ); } return pGrenade; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::InitGrenade( const Vector &velocity, const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo ) { InitGrenade( velocity, angVelocity, pOwner, weaponInfo.GetWeaponData( TF_WEAPON_PRIMARY_MODE ).m_nDamage, weaponInfo.m_flDamageRadius ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::InitGrenade( const Vector &velocity, const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const int iDamage, const float flRadius ) { // We can't use OwnerEntity for grenades, because then the owner can't shoot them with his hitscan weapons (due to collide rules) // Thrower is used to store the person who threw the grenade, for damage purposes. SetOwnerEntity( NULL ); SetThrower( pOwner ); SetupInitialTransmittedGrenadeVelocity( velocity ); SetGravity( 0.4f/*BaseClass::GetGrenadeGravity()*/ ); SetFriction( 0.2f/*BaseClass::GetGrenadeFriction()*/ ); SetElasticity( 0.45f/*BaseClass::GetGrenadeElasticity()*/ ); SetDamage( iDamage ); SetDamageRadius( flRadius ); ChangeTeam( pOwner ? pOwner->GetTeamNumber() : TEAM_UNASSIGNED ); IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); if ( pPhysicsObject ) { pPhysicsObject->AddVelocity( &velocity, &angVelocity ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::Spawn( void ) { // Base class spawn. BaseClass::Spawn(); // So it will collide with physics props! SetSolidFlags( FSOLID_NOT_STANDABLE ); SetSolid( SOLID_BBOX ); AddEffects( EF_NOSHADOW ); // Set the grenade size here. UTIL_SetSize( this, Vector( -2.0f, -2.0f, -2.0f ), Vector( 2.0f, 2.0f, 2.0f ) ); // Set the movement type. SetCollisionGroup( TF_COLLISIONGROUP_GRENADES ); VPhysicsInitNormal( SOLID_BBOX, 0, false ); m_takedamage = DAMAGE_EVENTS_ONLY; // Set the team. ChangeTeam( GetThrower() ? GetThrower()->GetTeamNumber() : TEAM_UNASSIGNED ); // Set skin based on team ( red = 1, blue = 2 ) m_nSkin = ( GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0; m_flDestroyableTime = gpGlobals->curtime + TF_GRENADE_DESTROYABLE_TIMER; // Setup the think and touch functions (see CBaseEntity). SetThink( &CTFWeaponBaseGrenadeProj::DetonateThink ); SetNextThink( gpGlobals->curtime + 0.2 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- #define TF_GRENADE_JUMP_RADIUS 146 void CTFWeaponBaseGrenadeProj::Explode( trace_t *pTrace, int bitsDamageType ) { if ( ShouldNotDetonate() ) { Destroy(); return; } SetModelName( NULL_STRING );//invisible AddSolidFlags( FSOLID_NOT_SOLID ); m_takedamage = DAMAGE_NO; // Pull out of the wall a bit if ( pTrace->fraction != 1.0 ) { SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) ); } CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), BASEGRENADE_EXPLOSION_VOLUME, 3.0 ); // Explosion effect on client Vector vecOrigin = GetAbsOrigin(); CPVSFilter filter( vecOrigin ); item_definition_index_t ownerWeaponDefIndex = INVALID_ITEM_DEF_INDEX; CTFWeaponBase *pWeapon = dynamic_cast(GetOriginalLauncher()); if (pWeapon) { ownerWeaponDefIndex = pWeapon->GetAttributeContainer()->GetItem()->GetItemDefIndex(); } // Halloween Custom Spell Effect int iHalloweenSpell = 0; int iCustomParticleIndex = GetCustomParticleIndex(); if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( m_hLauncher, iHalloweenSpell, halloween_pumpkin_explosions ); if ( iHalloweenSpell > 0 ) { iCustomParticleIndex = GetParticleSystemIndex( "halloween_explosion" ); } } int iLargeExplosion = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( m_hLauncher, iLargeExplosion, use_large_smoke_explosion ); if ( iLargeExplosion > 0 ) { DispatchParticleEffect( "explosionTrail_seeds_mvm", GetAbsOrigin(), GetAbsAngles() ); DispatchParticleEffect( "fluidSmokeExpl_ring_mvm", GetAbsOrigin(), GetAbsAngles() ); } if ( UseImpactNormal() ) { if ( pTrace->m_pEnt && pTrace->m_pEnt->IsPlayer() ) { TE_TFExplosion(filter, 0.0f, vecOrigin, GetImpactNormal(), GetWeaponID(), pTrace->m_pEnt->entindex(), ownerWeaponDefIndex, SPECIAL1, iCustomParticleIndex); } else { TE_TFExplosion(filter, 0.0f, vecOrigin, GetImpactNormal(), GetWeaponID(), kInvalidEHandleExplosion, ownerWeaponDefIndex, SPECIAL1, iCustomParticleIndex); } } else { if ( pTrace->m_pEnt && pTrace->m_pEnt->IsPlayer() ) { TE_TFExplosion(filter, 0.0f, vecOrigin, pTrace->plane.normal, GetWeaponID(), pTrace->m_pEnt->entindex(), ownerWeaponDefIndex, SPECIAL1, iCustomParticleIndex); } else { TE_TFExplosion(filter, 0.0f, vecOrigin, pTrace->plane.normal, GetWeaponID(), kInvalidEHandleExplosion, ownerWeaponDefIndex, SPECIAL1, iCustomParticleIndex); } } // Use the thrower's position as the reported position Vector vecReported = GetThrower() ? GetThrower()->GetAbsOrigin() : vec3_origin; int nCustomDamage = GetDamageCustom(); CTakeDamageInfo info( this, GetThrower(), m_hLauncher, GetBlastForce(), GetAbsOrigin(), m_flDamage, bitsDamageType, nCustomDamage, &vecReported ); float flRadius = GetDamageRadius(); #ifdef STAGING_ONLY if ( tf_grenade_show_radius.GetBool() ) { DrawRadius( flRadius ); } #endif CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, flRadius, NULL, TF_GRENADE_JUMP_RADIUS ); TFGameRules()->RadiusDamage( radiusinfo ); // Don't decal players with scorch. if ( pTrace->m_pEnt && !pTrace->m_pEnt->IsPlayer() ) { UTIL_DecalTrace( pTrace, "Scorch" ); } if ( pTrace->m_pEnt && pTrace->m_pEnt->IsPlayer() && GetThrower() ) { CTFPlayer *pTarget = ToTFPlayer( GetEnemy() ); if ( pTarget ) { if ( pTarget->GetTeamNumber() != GetThrower()->GetTeamNumber() ) { IGameEvent *event = gameeventmanager->CreateEvent( "projectile_direct_hit" ); if ( event ) { event->SetInt( "attacker", GetThrower()->entindex() ); event->SetInt( "victim", pTarget->entindex() ); gameeventmanager->FireEvent( event, true ); } } } } SetThink( &CBaseGrenade::SUB_Remove ); SetTouch( NULL ); AddEffects( EF_NODRAW ); SetAbsVelocity( vec3_origin ); SetNextThink( gpGlobals->curtime ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFWeaponBaseGrenadeProj::OnTakeDamage( const CTakeDamageInfo &info ) { CTakeDamageInfo info2 = info; // Reduce explosion damage so that we don't get knocked too far if ( info.GetDamageType() & DMG_BLAST ) { info2.ScaleDamageForce( 0.05 ); } // We need to skip back to the base entity take damage, because // CBaseCombatCharacter doesn't, which prevents us from reacting // to physics impact damage. return CBaseEntity::OnTakeDamage( info2 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::DetonateThink( void ) { if ( !IsInWorld() ) { Remove( ); return; } if ( gpGlobals->curtime > m_flDetonateTime ) { Detonate(); return; } SetNextThink( gpGlobals->curtime + 0.2 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::Detonate( void ) { 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() ); if ( GetShakeAmplitude() ) { UTIL_ScreenShake( GetAbsOrigin(), GetShakeAmplitude(), 150.0, 1.0, GetShakeRadius(), SHAKE_START ); } } //----------------------------------------------------------------------------- // Purpose: Sets the time at which the grenade will explode. //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::SetDetonateTimerLength( float timer ) { float fFuseMult = 1.0f; if ( GetOwnerEntity() ) { CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), fFuseMult, fuse_mult ); } m_flDetonateTime = gpGlobals->curtime + ( timer * fFuseMult ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity ) { //Assume all surfaces have the same elasticity float flSurfaceElasticity = 1.0; //Don't bounce off of players with perfect elasticity if( trace.m_pEnt && trace.m_pEnt->IsPlayer() ) { flSurfaceElasticity = 0.3; } #if 0 // if its breakable glass and we kill it, don't bounce. // give some damage to the glass, and if it breaks, pass // through it. bool breakthrough = false; if( trace.m_pEnt && FClassnameIs( trace.m_pEnt, "func_breakable" ) ) { breakthrough = true; } if( trace.m_pEnt && FClassnameIs( trace.m_pEnt, "func_breakable_surf" ) ) { breakthrough = true; } if (breakthrough) { CTakeDamageInfo info( this, this, 10, DMG_CLUB ); trace.m_pEnt->DispatchTraceAttack( info, GetAbsVelocity(), &trace ); ApplyMultiDamage(); if( trace.m_pEnt->m_iHealth <= 0 ) { // slow our flight a little bit Vector vel = GetAbsVelocity(); vel *= 0.4; SetAbsVelocity( vel ); return; } } #endif float flTotalElasticity = GetElasticity() * flSurfaceElasticity; flTotalElasticity = clamp( flTotalElasticity, 0.0f, 0.9f ); // NOTE: A backoff of 2.0f is a reflection Vector vecAbsVelocity; PhysicsClipVelocity( GetAbsVelocity(), trace.plane.normal, vecAbsVelocity, 2.0f ); vecAbsVelocity *= flTotalElasticity; // Get the total velocity (player + conveyors, etc.) VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity ); float flSpeedSqr = DotProduct( vecVelocity, vecVelocity ); // Stop if on ground. if ( trace.plane.normal.z > 0.7f ) // Floor { // Verify that we have an entity. CBaseEntity *pEntity = trace.m_pEnt; Assert( pEntity ); SetAbsVelocity( vecAbsVelocity ); if ( flSpeedSqr < ( 30 * 30 ) ) { if ( pEntity->IsStandable() ) { SetGroundEntity( pEntity ); } // Reset velocities. SetAbsVelocity( vec3_origin ); SetLocalAngularVelocity( vec3_angle ); //align to the ground so we're not standing on end QAngle angle; VectorAngles( trace.plane.normal, angle ); // rotate randomly in yaw angle[1] = random->RandomFloat( 0, 360 ); // TFTODO: rotate around trace.plane.normal SetAbsAngles( angle ); } else { Vector vecDelta = GetBaseVelocity() - vecAbsVelocity; Vector vecBaseDir = GetBaseVelocity(); VectorNormalize( vecBaseDir ); float flScale = vecDelta.Dot( vecBaseDir ); VectorScale( vecAbsVelocity, ( 1.0f - trace.fraction ) * gpGlobals->frametime, vecVelocity ); VectorMA( vecVelocity, ( 1.0f - trace.fraction ) * gpGlobals->frametime, GetBaseVelocity() * flScale, vecVelocity ); PhysicsPushEntity( vecVelocity, &trace ); } } else { // If we get *too* slow, we'll stick without ever coming to rest because // we'll get pushed down by gravity faster than we can escape from the wall. if ( flSpeedSqr < ( 30 * 30 ) ) { // Reset velocities. SetAbsVelocity( vec3_origin ); SetLocalAngularVelocity( vec3_angle ); } else { SetAbsVelocity( vecAbsVelocity ); } } BounceSound(); #if 0 // tell the bots a grenade has bounced CCSPlayer *player = ToCSPlayer(GetThrower()); if ( player ) { KeyValues *event = new KeyValues( "grenade_bounce" ); event->SetInt( "userid", player->GetUserID() ); gameeventmanager->FireEventServerOnly( event ); } #endif } bool CTFWeaponBaseGrenadeProj::ShouldNotDetonate( void ) { return InNoGrenadeZone( this ); } void CTFWeaponBaseGrenadeProj::Destroy( bool bBlinkOut, bool bBreak ) { if ( bBreak ) { CPVSFilter filter( GetAbsOrigin() ); UserMessageBegin( filter, "BreakModelRocketDud" ); WRITE_SHORT( GetModelIndex() ); WRITE_VEC3COORD( GetAbsOrigin() ); WRITE_ANGLES( GetAbsAngles() ); MessageEnd(); } // Kill it SetThink( &BaseClass::SUB_Remove ); SetNextThink( gpGlobals->curtime ); SetTouch( NULL ); AddEffects( EF_NODRAW ); if ( bBlinkOut ) { // Sprite flash CSprite *pGlowSprite = CSprite::SpriteCreate( NOGRENADE_SPRITE, GetAbsOrigin(), false ); if ( pGlowSprite ) { pGlowSprite->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxFadeFast ); pGlowSprite->SetThink( &CSprite::SUB_Remove ); pGlowSprite->SetNextThink( gpGlobals->curtime + 1.0 ); } } } //----------------------------------------------------------------------------- // Purpose: This will hit only things that are in newCollisionGroup, but NOT in collisionGroupAlreadyChecked // Always ignores other grenade projectiles. //----------------------------------------------------------------------------- class CTraceFilterCollisionGrenades : public CTraceFilterEntitiesOnly { public: // It does have a base, but we'll never network anything below here.. DECLARE_CLASS_NOBASE( CTraceFilterCollisionGrenades ); CTraceFilterCollisionGrenades( const IHandleEntity *passentity, const IHandleEntity *passentity2 ) : m_pPassEnt(passentity), m_pPassEnt2(passentity2) { } virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) { if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) ) return false; CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); if ( pEntity ) { if ( pEntity == m_pPassEnt2 ) return false; if ( pEntity->GetCollisionGroup() == TF_COLLISIONGROUP_GRENADES ) return false; if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_ROCKETS ) return false; if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) return false; if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_RESPAWNROOMS ) return false; if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_NONE ) return false; return true; } return true; } protected: const IHandleEntity *m_pPassEnt; const IHandleEntity *m_pPassEnt2; }; //----------------------------------------------------------------------------- // Purpose: Grenades aren't solid to players, so players don't get stuck on // them when they're lying on the ground. We still want thrown grenades // to bounce of players though, so manually trace ahead and see if we'd // hit something that we'd like the grenade to "collide" with. //----------------------------------------------------------------------------- void CTFWeaponBaseGrenadeProj::VPhysicsUpdate( IPhysicsObject *pPhysics ) { BaseClass::VPhysicsUpdate( pPhysics ); Vector vel; AngularImpulse angVel; pPhysics->GetVelocity( &vel, &angVel ); Vector start = GetAbsOrigin(); // find all entities that my collision group wouldn't hit, but COLLISION_GROUP_NONE would and bounce off of them as a ray cast CTraceFilterCollisionGrenades filter( this, GetThrower() ); ITraceFilter *pFilterChain = NULL; CTraceFilterIgnoreFriendlyCombatItems filterCombatItems( this, COLLISION_GROUP_NONE, GetTeamNumber(), true ); if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) { pFilterChain = &filterCombatItems; } CTraceFilterChain filterChain( &filter, pFilterChain ); trace_t tr; UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filterChain, &tr ); bool bHitEnemy = tr.m_pEnt && tr.m_pEnt->GetTeamNumber() == GetEnemyTeam( GetTeamNumber() ); bool bHitFriendly = tr.m_pEnt && tr.m_pEnt->GetTeamNumber() == GetTeamNumber() && CanCollideWithTeammates(); // Combat items are solid to enemy projectiles and bullets if ( bHitEnemy && tr.m_pEnt->IsCombatItem() ) { if ( IsAllowedToExplode() ) { Explode( &tr, GetDamageType() ); } else { BounceOff( pPhysics ); } return; } if ( tr.startsolid ) { if ( bHitEnemy ) { Touch( tr.m_pEnt ); } else if ( !m_bInSolid && bHitFriendly ) { BounceOff( pPhysics ); } m_bInSolid = true; return; } m_bInSolid = false; if ( tr.DidHit() ) { Touch( tr.m_pEnt ); if ( bHitFriendly || bHitEnemy ) { // reflect velocity around normal vel = -2.0f * tr.plane.normal * DotProduct(vel,tr.plane.normal) + vel; // absorb 80% in impact vel *= GetElasticity(); if ( bHitEnemy == true ) { vel *= 0.5f; } angVel *= -0.5f; pPhysics->SetVelocity( &vel, &angVel ); } } } #ifdef STAGING_ONLY void CTFWeaponBaseGrenadeProj::DrawRadius( float flRadius ) { Vector pos = GetAbsOrigin(); int r = 255; int g = 0, b = 0; float flLifetime = tf_grenade_show_radius_time.GetFloat(); bool bDepthTest = true; Vector edge, lastEdge; NDebugOverlay::Line( pos, pos + Vector( 0, 0, 50 ), r, g, b, !bDepthTest, flLifetime ); lastEdge = Vector( flRadius + pos.x, pos.y, pos.z ); float angle; for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) { edge.x = flRadius * cos( DEG2RAD( angle ) ) + pos.x; edge.y = pos.y; edge.z = flRadius * sin( DEG2RAD( angle ) ) + pos.z; NDebugOverlay::Line( edge, lastEdge, r, g, b, !bDepthTest, flLifetime ); lastEdge = edge; } lastEdge = Vector( pos.x, flRadius + pos.y, pos.z ); for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) { edge.x = pos.x; edge.y = flRadius * cos( DEG2RAD( angle ) ) + pos.y; edge.z = flRadius * sin( DEG2RAD( angle ) ) + pos.z; NDebugOverlay::Line( edge, lastEdge, r, g, b, !bDepthTest, flLifetime ); lastEdge = edge; } lastEdge = Vector( pos.x, flRadius + pos.y, pos.z ); for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) { edge.x = flRadius * cos( DEG2RAD( angle ) ) + pos.x; edge.y = flRadius * sin( DEG2RAD( angle ) ) + pos.y; edge.z = pos.z; NDebugOverlay::Line( edge, lastEdge, r, g, b, !bDepthTest, flLifetime ); lastEdge = edge; } } #endif // STAGING_ONLY #endif