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
25 KiB
861 lines
25 KiB
//========= 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<CTFWeaponBaseGrenadeProj*>( 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<CTFWeaponBase *>(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
|
|
|