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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "dod_basegrenade.h"
  8. #include "dod_player.h"
  9. #include "dod_gamerules.h"
  10. #include "func_break.h"
  11. #include "physics_saverestore.h"
  12. #include "grenadetrail.h"
  13. #include "fx_dod_shared.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. float GetCurrentGravity( void );
  17. ConVar dod_grenadegravity( "dod_grenadegravity", "-420", FCVAR_CHEAT, "gravity applied to grenades", true, -2000, true, -300 );
  18. extern ConVar dod_bonusround;
  19. IMotionEvent::simresult_e CGrenadeController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
  20. {
  21. linear.x = linear.y = 0;
  22. linear.z = dod_grenadegravity.GetFloat();
  23. angular.x = angular.y = angular.z = 0;
  24. return SIM_GLOBAL_ACCELERATION;
  25. }
  26. BEGIN_SIMPLE_DATADESC( CGrenadeController )
  27. END_DATADESC()
  28. BEGIN_DATADESC( CDODBaseGrenade )
  29. DEFINE_THINKFUNC( DetonateThink ),
  30. DEFINE_EMBEDDED( m_GrenadeController ),
  31. DEFINE_PHYSPTR( m_pMotionController ), // probably not necessary
  32. END_DATADESC()
  33. IMPLEMENT_SERVERCLASS_ST( CDODBaseGrenade, DT_DODBaseGrenade )
  34. SendPropVector( SENDINFO( m_vInitialVelocity ),
  35. 20, // nbits
  36. 0, // flags
  37. -3000, // low value
  38. 3000 // high value
  39. )
  40. END_SEND_TABLE()
  41. CDODBaseGrenade::CDODBaseGrenade()
  42. {
  43. }
  44. CDODBaseGrenade::~CDODBaseGrenade( void )
  45. {
  46. if ( m_pMotionController != NULL )
  47. {
  48. physenv->DestroyMotionController( m_pMotionController );
  49. m_pMotionController = NULL;
  50. }
  51. }
  52. void CDODBaseGrenade::Spawn( void )
  53. {
  54. m_bUseVPhysics = true;
  55. BaseClass::Spawn();
  56. SetSolid( SOLID_BBOX ); // So it will collide with physics props!
  57. UTIL_SetSize( this, Vector(-4,-4,-4), Vector(4,4,4) );
  58. if( m_bUseVPhysics )
  59. {
  60. SetCollisionGroup( COLLISION_GROUP_WEAPON );
  61. IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_BBOX, 0, false );
  62. if ( pPhysicsObject )
  63. {
  64. m_pMotionController = physenv->CreateMotionController( &m_GrenadeController );
  65. m_pMotionController->AttachObject( pPhysicsObject, true );
  66. pPhysicsObject->EnableGravity( false );
  67. }
  68. m_takedamage = DAMAGE_EVENTS_ONLY;
  69. }
  70. else
  71. {
  72. SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
  73. m_takedamage = DAMAGE_NO;
  74. }
  75. AddSolidFlags( FSOLID_NOT_STANDABLE );
  76. m_iHealth = 1;
  77. SetFriction( GetGrenadeFriction() );
  78. SetElasticity( GetGrenadeElasticity() );
  79. // Remember our owner's team
  80. ChangeTeam( GetThrower()->GetTeamNumber() );
  81. m_flDamage = 150;
  82. m_DmgRadius = m_flDamage * 2.5f;
  83. // Don't collide with players on the owner's team for the first bit of our life
  84. m_flCollideWithTeammatesTime = gpGlobals->curtime + 0.25;
  85. m_bCollideWithTeammates = false;
  86. SetThink( &CDODBaseGrenade::DetonateThink );
  87. SetNextThink( gpGlobals->curtime + 0.1 );
  88. }
  89. void CDODBaseGrenade::Precache( void )
  90. {
  91. BaseClass::Precache();
  92. PrecacheParticleSystem( "grenadetrail" );
  93. PrecacheParticleSystem( "riflegrenadetrail" );
  94. PrecacheParticleSystem( "explosioncore_midair" );
  95. PrecacheParticleSystem( "explosioncore_floor" );
  96. }
  97. void CDODBaseGrenade::DetonateThink( void )
  98. {
  99. if (!IsInWorld())
  100. {
  101. Remove( );
  102. return;
  103. }
  104. if ( gpGlobals->curtime > m_flCollideWithTeammatesTime && m_bCollideWithTeammates == false )
  105. {
  106. m_bCollideWithTeammates = true;
  107. }
  108. Vector foo;
  109. AngularImpulse a;
  110. VPhysicsGetObject()->GetVelocity( &foo, &a );
  111. if( gpGlobals->curtime > m_flDetonateTime )
  112. {
  113. Detonate();
  114. return;
  115. }
  116. if (GetWaterLevel() != 0)
  117. {
  118. SetAbsVelocity( GetAbsVelocity() * 0.5 );
  119. }
  120. SetNextThink( gpGlobals->curtime + 0.2 );
  121. }
  122. //Sets the time at which the grenade will explode
  123. void CDODBaseGrenade::SetDetonateTimerLength( float timer )
  124. {
  125. m_flDetonateTime = gpGlobals->curtime + timer;
  126. }
  127. void CDODBaseGrenade::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity )
  128. {
  129. //Assume all surfaces have the same elasticity
  130. float flSurfaceElasticity = 1.0;
  131. //Don't bounce off of players with perfect elasticity
  132. if( trace.m_pEnt && trace.m_pEnt->IsPlayer() )
  133. {
  134. flSurfaceElasticity = 0.3;
  135. }
  136. float flTotalElasticity = GetElasticity() * flSurfaceElasticity;
  137. flTotalElasticity = clamp( flTotalElasticity, 0.0f, 0.9f );
  138. // NOTE: A backoff of 2.0f is a reflection
  139. Vector vecAbsVelocity;
  140. PhysicsClipVelocity( GetAbsVelocity(), trace.plane.normal, vecAbsVelocity, 2.0f );
  141. vecAbsVelocity *= flTotalElasticity;
  142. // Get the total velocity (player + conveyors, etc.)
  143. VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity );
  144. float flSpeedSqr = DotProduct( vecVelocity, vecVelocity );
  145. // Stop if on ground.
  146. if ( trace.plane.normal.z > 0.7f ) // Floor
  147. {
  148. // Verify that we have an entity.
  149. CBaseEntity *pEntity = trace.m_pEnt;
  150. Assert( pEntity );
  151. // Are we on the ground?
  152. if ( vecVelocity.z < ( GetCurrentGravity() * gpGlobals->frametime ) )
  153. {
  154. if ( pEntity->IsStandable() )
  155. {
  156. SetGroundEntity( pEntity );
  157. }
  158. vecAbsVelocity.z = 0.0f;
  159. }
  160. SetAbsVelocity( vecAbsVelocity );
  161. if ( flSpeedSqr < ( 30 * 30 ) )
  162. {
  163. if ( pEntity->IsStandable() )
  164. {
  165. SetGroundEntity( pEntity );
  166. }
  167. // Reset velocities.
  168. SetAbsVelocity( vec3_origin );
  169. SetLocalAngularVelocity( vec3_angle );
  170. }
  171. else
  172. {
  173. Vector vecDelta = GetBaseVelocity() - vecAbsVelocity;
  174. Vector vecBaseDir = GetBaseVelocity();
  175. VectorNormalize( vecBaseDir );
  176. float flScale = vecDelta.Dot( vecBaseDir );
  177. VectorScale( vecAbsVelocity, ( 1.0f - trace.fraction ) * gpGlobals->frametime, vecVelocity );
  178. VectorMA( vecVelocity, ( 1.0f - trace.fraction ) * gpGlobals->frametime, GetBaseVelocity() * flScale, vecVelocity );
  179. PhysicsPushEntity( vecVelocity, &trace );
  180. }
  181. }
  182. else
  183. {
  184. // If we get *too* slow, we'll stick without ever coming to rest because
  185. // we'll get pushed down by gravity faster than we can escape from the wall.
  186. if ( flSpeedSqr < ( 30 * 30 ) )
  187. {
  188. // Reset velocities.
  189. SetAbsVelocity( vec3_origin );
  190. SetLocalAngularVelocity( vec3_angle );
  191. }
  192. else
  193. {
  194. SetAbsVelocity( vecAbsVelocity );
  195. }
  196. }
  197. BounceSound();
  198. }
  199. char *CDODBaseGrenade::GetExplodingClassname( void )
  200. {
  201. Assert( !"Baseclass must implement this" );
  202. return NULL;
  203. }
  204. void CDODBaseGrenade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  205. {
  206. if ( !CanBePickedUp() )
  207. return;
  208. if ( !pActivator->IsPlayer() )
  209. return;
  210. CDODPlayer *pPlayer = ToDODPlayer( pActivator );
  211. //Don't pick up grenades while deployed
  212. CBaseCombatWeapon *pWpn = pPlayer->GetActiveWeapon();
  213. if ( pWpn && !pWpn->CanHolster() )
  214. {
  215. return;
  216. }
  217. DODRoundState state = DODGameRules()->State_Get();
  218. if ( dod_bonusround.GetBool() )
  219. {
  220. int team = pPlayer->GetTeamNumber();
  221. // if its after the round and bonus round is on, we can only pick it up if we are winners
  222. if ( team == TEAM_ALLIES && state == STATE_AXIS_WIN )
  223. return;
  224. if ( team == TEAM_AXIS && state == STATE_ALLIES_WIN )
  225. return;
  226. }
  227. else
  228. {
  229. // if its after the round, and bonus round is off, don't allow anyone to pick it up
  230. if ( state != STATE_RND_RUNNING )
  231. return;
  232. }
  233. OnPickedUp();
  234. char *szClsName = GetExplodingClassname();
  235. Assert( szClsName );
  236. CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>( pPlayer->GiveNamedItem( szClsName ) );
  237. Assert( pWeapon && "Wpn pointer has to be valid for us to pick up this grenade" );
  238. if( pWeapon )
  239. {
  240. variant_t flDetTime;
  241. flDetTime.SetFloat( m_flDetonateTime );
  242. pWeapon->AcceptInput( "DetonateTime", this, this, flDetTime, 0 );
  243. #ifdef DBGFLAG_ASSERT
  244. bool bSuccess =
  245. #endif
  246. pPlayer->Weapon_Switch( pWeapon );
  247. Assert( bSuccess );
  248. //Remove the one we picked up
  249. SetThink( NULL );
  250. UTIL_Remove( this );
  251. }
  252. }
  253. int CDODBaseGrenade::OnTakeDamage( const CTakeDamageInfo &info )
  254. {
  255. if( info.GetDamageType() & DMG_BULLET )
  256. {
  257. // Don't allow players to shoot grenades
  258. return 0;
  259. }
  260. else if( info.GetDamageType() & DMG_BLAST )
  261. {
  262. // Don't allow explosion force to move grenades
  263. return 0;
  264. }
  265. VPhysicsTakeDamage( info );
  266. return BaseClass::OnTakeDamage( info );
  267. }
  268. void CDODBaseGrenade::Detonate()
  269. {
  270. // Don't explode after the round has ended
  271. if ( dod_bonusround.GetBool() == false && DODGameRules()->State_Get() != STATE_RND_RUNNING )
  272. {
  273. SetDamage( 0 );
  274. }
  275. // stun players in a radius
  276. const float flStunDamage = 100;
  277. CTakeDamageInfo info( this, GetThrower(), GetBlastForce(), GetAbsOrigin(), flStunDamage, DMG_STUN );
  278. DODGameRules()->RadiusStun( info, GetAbsOrigin(), m_DmgRadius );
  279. BaseClass::Detonate();
  280. }
  281. bool CDODBaseGrenade::CreateVPhysics()
  282. {
  283. // Create the object in the physics system
  284. VPhysicsInitNormal( SOLID_BBOX, 0, false );
  285. return true;
  286. }
  287. void CDODBaseGrenade::Explode( trace_t *pTrace, int bitsDamageType )
  288. {
  289. SetModelName( NULL_STRING );//invisible
  290. AddSolidFlags( FSOLID_NOT_SOLID );
  291. m_takedamage = DAMAGE_NO;
  292. // Pull out of the wall a bit
  293. if ( pTrace->fraction != 1.0 )
  294. {
  295. SetAbsOrigin( pTrace->endpos + (pTrace->plane.normal * 0.6) );
  296. }
  297. // Explosion effect on client
  298. Vector vecOrigin = GetAbsOrigin();
  299. CPVSFilter filter( vecOrigin );
  300. TE_DODExplosion( filter, 0.0f, vecOrigin, pTrace->plane.normal );
  301. // Use the thrower's position as the reported position
  302. Vector vecReported = GetThrower() ? GetThrower()->GetAbsOrigin() : vec3_origin;
  303. CTakeDamageInfo info( this, GetThrower(), GetBlastForce(), GetAbsOrigin(), m_flDamage, bitsDamageType, 0, &vecReported );
  304. RadiusDamage( info, vecOrigin, GetDamageRadius(), CLASS_NONE, NULL );
  305. // Don't decal players with scorch.
  306. if ( pTrace->m_pEnt && !pTrace->m_pEnt->IsPlayer() )
  307. {
  308. UTIL_DecalTrace( pTrace, "Scorch" );
  309. }
  310. SetThink( &CBaseGrenade::SUB_Remove );
  311. SetTouch( NULL );
  312. AddEffects( EF_NODRAW );
  313. SetAbsVelocity( vec3_origin );
  314. SetNextThink( gpGlobals->curtime );
  315. }
  316. // this will hit only things that are in newCollisionGroup, but NOT in collisionGroupAlreadyChecked
  317. class CTraceFilterCollisionGroupDelta : public CTraceFilterEntitiesOnly
  318. {
  319. public:
  320. // It does have a base, but we'll never network anything below here..
  321. DECLARE_CLASS_NOBASE( CTraceFilterCollisionGroupDelta );
  322. CTraceFilterCollisionGroupDelta( const IHandleEntity *passentity, int collisionGroupAlreadyChecked, int newCollisionGroup )
  323. : m_pPassEnt(passentity), m_collisionGroupAlreadyChecked( collisionGroupAlreadyChecked ), m_newCollisionGroup( newCollisionGroup )
  324. {
  325. }
  326. virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  327. {
  328. if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
  329. return false;
  330. CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
  331. if ( pEntity )
  332. {
  333. if ( g_pGameRules->ShouldCollide( m_collisionGroupAlreadyChecked, pEntity->GetCollisionGroup() ) )
  334. return false;
  335. if ( g_pGameRules->ShouldCollide( m_newCollisionGroup, pEntity->GetCollisionGroup() ) )
  336. return true;
  337. }
  338. return false;
  339. }
  340. protected:
  341. const IHandleEntity *m_pPassEnt;
  342. int m_collisionGroupAlreadyChecked;
  343. int m_newCollisionGroup;
  344. };
  345. const float GRENADE_COEFFICIENT_OF_RESTITUTION = 0.2f;
  346. void CDODBaseGrenade::VPhysicsUpdate( IPhysicsObject *pPhysics )
  347. {
  348. BaseClass::VPhysicsUpdate( pPhysics );
  349. Vector vel;
  350. AngularImpulse angVel;
  351. pPhysics->GetVelocity( &vel, &angVel );
  352. Vector start = GetAbsOrigin();
  353. // find all entities that my collision group wouldn't hit, but COLLISION_GROUP_NONE would and bounce off of them as a ray cast
  354. CTraceFilterCollisionGroupDelta filter( this, GetCollisionGroup(), COLLISION_GROUP_NONE );
  355. trace_t tr;
  356. UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr );
  357. bool bHitTeammate = false;
  358. if ( m_bCollideWithTeammates == false && tr.m_pEnt && tr.m_pEnt->IsPlayer() && tr.m_pEnt->GetTeamNumber() == GetTeamNumber() )
  359. {
  360. bHitTeammate = true;
  361. }
  362. if ( tr.startsolid )
  363. {
  364. if ( m_bInSolid == false && bHitTeammate == false )
  365. {
  366. // UNDONE: Do a better contact solution that uses relative velocity?
  367. vel *= -GRENADE_COEFFICIENT_OF_RESTITUTION; // bounce backwards
  368. pPhysics->SetVelocity( &vel, NULL );
  369. }
  370. m_bInSolid = true;
  371. return;
  372. }
  373. m_bInSolid = false;
  374. if ( tr.DidHit() && bHitTeammate == false )
  375. {
  376. Vector dir = vel;
  377. VectorNormalize(dir);
  378. float flPercent = vel.Length() / 2000;
  379. float flDmg = 5 * flPercent;
  380. // send a tiny amount of damage so the character will react to getting bonked
  381. CTakeDamageInfo info( this, GetThrower(), pPhysics->GetMass() * vel, GetAbsOrigin(), flDmg, DMG_CRUSH );
  382. tr.m_pEnt->DispatchTraceAttack( info, dir, &tr );
  383. ApplyMultiDamage();
  384. if ( vel.Length() > 1000 )
  385. {
  386. CTakeDamageInfo stunInfo( this, GetThrower(), vec3_origin, GetAbsOrigin(), flDmg, DMG_STUN );
  387. tr.m_pEnt->TakeDamage( stunInfo );
  388. }
  389. // reflect velocity around normal
  390. vel = -2.0f * tr.plane.normal * DotProduct(vel,tr.plane.normal) + vel;
  391. // absorb 80% in impact
  392. vel *= GetElasticity();
  393. angVel *= -0.5f;
  394. pPhysics->SetVelocity( &vel, &angVel );
  395. }
  396. }
  397. float CDODBaseGrenade::GetElasticity( void )
  398. {
  399. return GRENADE_COEFFICIENT_OF_RESTITUTION;
  400. }
  401. const float DOD_GRENADE_WINDOW_BREAK_DAMPING_AMOUNT = 0.5f;
  402. // If we hit a window, let it break and continue on our way
  403. // with a damped speed
  404. void CDODBaseGrenade::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  405. {
  406. CBreakable *pBreakable = dynamic_cast<CBreakable*>( pEvent->pEntities[!index] );
  407. if ( pBreakable && pBreakable->GetMaterialType() == matGlass && VPhysicsGetObject() )
  408. {
  409. // don't stop, go through this entity after breaking it
  410. Vector dampedVelocity = DOD_GRENADE_WINDOW_BREAK_DAMPING_AMOUNT * pEvent->preVelocity[index];
  411. VPhysicsGetObject()->SetVelocity( &dampedVelocity, &pEvent->preAngularVelocity[index] );
  412. }
  413. else
  414. BaseClass::VPhysicsCollision( index, pEvent );
  415. }