Team Fortress 2 Source Code as on 22/4/2020
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.
 
 
 
 
 
 

526 lines
14 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "dod_basegrenade.h"
#include "dod_player.h"
#include "dod_gamerules.h"
#include "func_break.h"
#include "physics_saverestore.h"
#include "grenadetrail.h"
#include "fx_dod_shared.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
float GetCurrentGravity( void );
ConVar dod_grenadegravity( "dod_grenadegravity", "-420", FCVAR_CHEAT, "gravity applied to grenades", true, -2000, true, -300 );
extern ConVar dod_bonusround;
IMotionEvent::simresult_e CGrenadeController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
{
linear.x = linear.y = 0;
linear.z = dod_grenadegravity.GetFloat();
angular.x = angular.y = angular.z = 0;
return SIM_GLOBAL_ACCELERATION;
}
BEGIN_SIMPLE_DATADESC( CGrenadeController )
END_DATADESC()
BEGIN_DATADESC( CDODBaseGrenade )
DEFINE_THINKFUNC( DetonateThink ),
DEFINE_EMBEDDED( m_GrenadeController ),
DEFINE_PHYSPTR( m_pMotionController ), // probably not necessary
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST( CDODBaseGrenade, DT_DODBaseGrenade )
SendPropVector( SENDINFO( m_vInitialVelocity ),
20, // nbits
0, // flags
-3000, // low value
3000 // high value
)
END_SEND_TABLE()
CDODBaseGrenade::CDODBaseGrenade()
{
}
CDODBaseGrenade::~CDODBaseGrenade( void )
{
if ( m_pMotionController != NULL )
{
physenv->DestroyMotionController( m_pMotionController );
m_pMotionController = NULL;
}
}
void CDODBaseGrenade::Spawn( void )
{
m_bUseVPhysics = true;
BaseClass::Spawn();
SetSolid( SOLID_BBOX ); // So it will collide with physics props!
UTIL_SetSize( this, Vector(-4,-4,-4), Vector(4,4,4) );
if( m_bUseVPhysics )
{
SetCollisionGroup( COLLISION_GROUP_WEAPON );
IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_BBOX, 0, false );
if ( pPhysicsObject )
{
m_pMotionController = physenv->CreateMotionController( &m_GrenadeController );
m_pMotionController->AttachObject( pPhysicsObject, true );
pPhysicsObject->EnableGravity( false );
}
m_takedamage = DAMAGE_EVENTS_ONLY;
}
else
{
SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
m_takedamage = DAMAGE_NO;
}
AddSolidFlags( FSOLID_NOT_STANDABLE );
m_iHealth = 1;
SetFriction( GetGrenadeFriction() );
SetElasticity( GetGrenadeElasticity() );
// Remember our owner's team
ChangeTeam( GetThrower()->GetTeamNumber() );
m_flDamage = 150;
m_DmgRadius = m_flDamage * 2.5f;
// Don't collide with players on the owner's team for the first bit of our life
m_flCollideWithTeammatesTime = gpGlobals->curtime + 0.25;
m_bCollideWithTeammates = false;
SetThink( &CDODBaseGrenade::DetonateThink );
SetNextThink( gpGlobals->curtime + 0.1 );
}
void CDODBaseGrenade::Precache( void )
{
BaseClass::Precache();
PrecacheParticleSystem( "grenadetrail" );
PrecacheParticleSystem( "riflegrenadetrail" );
PrecacheParticleSystem( "explosioncore_midair" );
PrecacheParticleSystem( "explosioncore_floor" );
}
void CDODBaseGrenade::DetonateThink( void )
{
if (!IsInWorld())
{
Remove( );
return;
}
if ( gpGlobals->curtime > m_flCollideWithTeammatesTime && m_bCollideWithTeammates == false )
{
m_bCollideWithTeammates = true;
}
Vector foo;
AngularImpulse a;
VPhysicsGetObject()->GetVelocity( &foo, &a );
if( gpGlobals->curtime > m_flDetonateTime )
{
Detonate();
return;
}
if (GetWaterLevel() != 0)
{
SetAbsVelocity( GetAbsVelocity() * 0.5 );
}
SetNextThink( gpGlobals->curtime + 0.2 );
}
//Sets the time at which the grenade will explode
void CDODBaseGrenade::SetDetonateTimerLength( float timer )
{
m_flDetonateTime = gpGlobals->curtime + timer;
}
void CDODBaseGrenade::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;
}
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 );
// Are we on the ground?
if ( vecVelocity.z < ( GetCurrentGravity() * gpGlobals->frametime ) )
{
if ( pEntity->IsStandable() )
{
SetGroundEntity( pEntity );
}
vecAbsVelocity.z = 0.0f;
}
SetAbsVelocity( vecAbsVelocity );
if ( flSpeedSqr < ( 30 * 30 ) )
{
if ( pEntity->IsStandable() )
{
SetGroundEntity( pEntity );
}
// Reset velocities.
SetAbsVelocity( vec3_origin );
SetLocalAngularVelocity( vec3_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();
}
char *CDODBaseGrenade::GetExplodingClassname( void )
{
Assert( !"Baseclass must implement this" );
return NULL;
}
void CDODBaseGrenade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if ( !CanBePickedUp() )
return;
if ( !pActivator->IsPlayer() )
return;
CDODPlayer *pPlayer = ToDODPlayer( pActivator );
//Don't pick up grenades while deployed
CBaseCombatWeapon *pWpn = pPlayer->GetActiveWeapon();
if ( pWpn && !pWpn->CanHolster() )
{
return;
}
DODRoundState state = DODGameRules()->State_Get();
if ( dod_bonusround.GetBool() )
{
int team = pPlayer->GetTeamNumber();
// if its after the round and bonus round is on, we can only pick it up if we are winners
if ( team == TEAM_ALLIES && state == STATE_AXIS_WIN )
return;
if ( team == TEAM_AXIS && state == STATE_ALLIES_WIN )
return;
}
else
{
// if its after the round, and bonus round is off, don't allow anyone to pick it up
if ( state != STATE_RND_RUNNING )
return;
}
OnPickedUp();
char *szClsName = GetExplodingClassname();
Assert( szClsName );
CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>( pPlayer->GiveNamedItem( szClsName ) );
Assert( pWeapon && "Wpn pointer has to be valid for us to pick up this grenade" );
if( pWeapon )
{
variant_t flDetTime;
flDetTime.SetFloat( m_flDetonateTime );
pWeapon->AcceptInput( "DetonateTime", this, this, flDetTime, 0 );
#ifdef DBGFLAG_ASSERT
bool bSuccess =
#endif
pPlayer->Weapon_Switch( pWeapon );
Assert( bSuccess );
//Remove the one we picked up
SetThink( NULL );
UTIL_Remove( this );
}
}
int CDODBaseGrenade::OnTakeDamage( const CTakeDamageInfo &info )
{
if( info.GetDamageType() & DMG_BULLET )
{
// Don't allow players to shoot grenades
return 0;
}
else if( info.GetDamageType() & DMG_BLAST )
{
// Don't allow explosion force to move grenades
return 0;
}
VPhysicsTakeDamage( info );
return BaseClass::OnTakeDamage( info );
}
void CDODBaseGrenade::Detonate()
{
// Don't explode after the round has ended
if ( dod_bonusround.GetBool() == false && DODGameRules()->State_Get() != STATE_RND_RUNNING )
{
SetDamage( 0 );
}
// stun players in a radius
const float flStunDamage = 100;
CTakeDamageInfo info( this, GetThrower(), GetBlastForce(), GetAbsOrigin(), flStunDamage, DMG_STUN );
DODGameRules()->RadiusStun( info, GetAbsOrigin(), m_DmgRadius );
BaseClass::Detonate();
}
bool CDODBaseGrenade::CreateVPhysics()
{
// Create the object in the physics system
VPhysicsInitNormal( SOLID_BBOX, 0, false );
return true;
}
void CDODBaseGrenade::Explode( trace_t *pTrace, int bitsDamageType )
{
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 * 0.6) );
}
// Explosion effect on client
Vector vecOrigin = GetAbsOrigin();
CPVSFilter filter( vecOrigin );
TE_DODExplosion( filter, 0.0f, vecOrigin, pTrace->plane.normal );
// Use the thrower's position as the reported position
Vector vecReported = GetThrower() ? GetThrower()->GetAbsOrigin() : vec3_origin;
CTakeDamageInfo info( this, GetThrower(), GetBlastForce(), GetAbsOrigin(), m_flDamage, bitsDamageType, 0, &vecReported );
RadiusDamage( info, vecOrigin, GetDamageRadius(), CLASS_NONE, NULL );
// Don't decal players with scorch.
if ( pTrace->m_pEnt && !pTrace->m_pEnt->IsPlayer() )
{
UTIL_DecalTrace( pTrace, "Scorch" );
}
SetThink( &CBaseGrenade::SUB_Remove );
SetTouch( NULL );
AddEffects( EF_NODRAW );
SetAbsVelocity( vec3_origin );
SetNextThink( gpGlobals->curtime );
}
// this will hit only things that are in newCollisionGroup, but NOT in collisionGroupAlreadyChecked
class CTraceFilterCollisionGroupDelta : public CTraceFilterEntitiesOnly
{
public:
// It does have a base, but we'll never network anything below here..
DECLARE_CLASS_NOBASE( CTraceFilterCollisionGroupDelta );
CTraceFilterCollisionGroupDelta( const IHandleEntity *passentity, int collisionGroupAlreadyChecked, int newCollisionGroup )
: m_pPassEnt(passentity), m_collisionGroupAlreadyChecked( collisionGroupAlreadyChecked ), m_newCollisionGroup( newCollisionGroup )
{
}
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
{
if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
return false;
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
if ( pEntity )
{
if ( g_pGameRules->ShouldCollide( m_collisionGroupAlreadyChecked, pEntity->GetCollisionGroup() ) )
return false;
if ( g_pGameRules->ShouldCollide( m_newCollisionGroup, pEntity->GetCollisionGroup() ) )
return true;
}
return false;
}
protected:
const IHandleEntity *m_pPassEnt;
int m_collisionGroupAlreadyChecked;
int m_newCollisionGroup;
};
const float GRENADE_COEFFICIENT_OF_RESTITUTION = 0.2f;
void CDODBaseGrenade::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
CTraceFilterCollisionGroupDelta filter( this, GetCollisionGroup(), COLLISION_GROUP_NONE );
trace_t tr;
UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr );
bool bHitTeammate = false;
if ( m_bCollideWithTeammates == false && tr.m_pEnt && tr.m_pEnt->IsPlayer() && tr.m_pEnt->GetTeamNumber() == GetTeamNumber() )
{
bHitTeammate = true;
}
if ( tr.startsolid )
{
if ( m_bInSolid == false && bHitTeammate == false )
{
// UNDONE: Do a better contact solution that uses relative velocity?
vel *= -GRENADE_COEFFICIENT_OF_RESTITUTION; // bounce backwards
pPhysics->SetVelocity( &vel, NULL );
}
m_bInSolid = true;
return;
}
m_bInSolid = false;
if ( tr.DidHit() && bHitTeammate == false )
{
Vector dir = vel;
VectorNormalize(dir);
float flPercent = vel.Length() / 2000;
float flDmg = 5 * flPercent;
// send a tiny amount of damage so the character will react to getting bonked
CTakeDamageInfo info( this, GetThrower(), pPhysics->GetMass() * vel, GetAbsOrigin(), flDmg, DMG_CRUSH );
tr.m_pEnt->DispatchTraceAttack( info, dir, &tr );
ApplyMultiDamage();
if ( vel.Length() > 1000 )
{
CTakeDamageInfo stunInfo( this, GetThrower(), vec3_origin, GetAbsOrigin(), flDmg, DMG_STUN );
tr.m_pEnt->TakeDamage( stunInfo );
}
// reflect velocity around normal
vel = -2.0f * tr.plane.normal * DotProduct(vel,tr.plane.normal) + vel;
// absorb 80% in impact
vel *= GetElasticity();
angVel *= -0.5f;
pPhysics->SetVelocity( &vel, &angVel );
}
}
float CDODBaseGrenade::GetElasticity( void )
{
return GRENADE_COEFFICIENT_OF_RESTITUTION;
}
const float DOD_GRENADE_WINDOW_BREAK_DAMPING_AMOUNT = 0.5f;
// If we hit a window, let it break and continue on our way
// with a damped speed
void CDODBaseGrenade::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
CBreakable *pBreakable = dynamic_cast<CBreakable*>( pEvent->pEntities[!index] );
if ( pBreakable && pBreakable->GetMaterialType() == matGlass && VPhysicsGetObject() )
{
// don't stop, go through this entity after breaking it
Vector dampedVelocity = DOD_GRENADE_WINDOW_BREAK_DAMPING_AMOUNT * pEvent->preVelocity[index];
VPhysicsGetObject()->SetVelocity( &dampedVelocity, &pEvent->preAngularVelocity[index] );
}
else
BaseClass::VPhysicsCollision( index, pEvent );
}