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.

861 lines
25 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "tf_weaponbase_grenadeproj.h"
  8. #include "tf_gamerules.h"
  9. // Client specific.
  10. #ifdef CLIENT_DLL
  11. #include "c_tf_player.h"
  12. // Server specific.
  13. #else
  14. #include "soundent.h"
  15. #include "te_effect_dispatch.h"
  16. #include "tf_player.h"
  17. #include "func_break.h"
  18. #include "func_nogrenades.h"
  19. #include "Sprite.h"
  20. #include "tf_fx.h"
  21. #include "halloween/merasmus/merasmus.h"
  22. #endif
  23. // memdbgon must be the last include file in a .cpp file!!!
  24. #include "tier0/memdbgon.h"
  25. //=============================================================================
  26. //
  27. // TF Grenade projectile tables.
  28. //
  29. // Server specific.
  30. #ifdef GAME_DLL
  31. BEGIN_DATADESC( CTFWeaponBaseGrenadeProj )
  32. DEFINE_THINKFUNC( DetonateThink ),
  33. END_DATADESC()
  34. #ifdef STAGING_ONLY
  35. ConVar tf_grenade_show_radius( "tf_grenade_show_radius", "0", FCVAR_CHEAT, "Render radius of grenades" );
  36. ConVar tf_grenade_show_radius_time( "tf_grenade_show_radius_time", "5.0", FCVAR_CHEAT, "Time to show grenade radius" );
  37. #endif // STAGING_ONLY
  38. extern void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
  39. extern void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
  40. #endif
  41. IMPLEMENT_NETWORKCLASS_ALIASED( TFWeaponBaseGrenadeProj, DT_TFWeaponBaseGrenadeProj )
  42. LINK_ENTITY_TO_CLASS( tf_weaponbase_grenade_proj, CTFWeaponBaseGrenadeProj );
  43. PRECACHE_REGISTER( tf_weaponbase_grenade_proj );
  44. BEGIN_NETWORK_TABLE( CTFWeaponBaseGrenadeProj, DT_TFWeaponBaseGrenadeProj )
  45. #ifdef CLIENT_DLL
  46. RecvPropVector( RECVINFO( m_vInitialVelocity ) ),
  47. RecvPropBool( RECVINFO( m_bCritical ) ),
  48. RecvPropInt( RECVINFO( m_iDeflected ) ),
  49. RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
  50. RecvPropQAngles( RECVINFO_NAME( m_angNetworkAngles, m_angRotation ) ),
  51. RecvPropEHandle( RECVINFO( m_hDeflectOwner )),
  52. #else
  53. SendPropVector( SENDINFO( m_vInitialVelocity ), 20 /*nbits*/, 0 /*flags*/, -3000 /*low value*/, 3000 /*high value*/ ),
  54. SendPropBool( SENDINFO( m_bCritical ) ),
  55. SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ),
  56. SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
  57. SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_COORD_MP_INTEGRAL|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
  58. SendPropQAngles (SENDINFO(m_angRotation), 6, SPROP_CHANGES_OFTEN, SendProxy_Angles ),
  59. SendPropInt( SENDINFO( m_iDeflected ), 4, SPROP_UNSIGNED ),
  60. SendPropEHandle(SENDINFO( m_hDeflectOwner )),
  61. #endif
  62. END_NETWORK_TABLE()
  63. //-----------------------------------------------------------------------------
  64. // Purpose: Constructor.
  65. //-----------------------------------------------------------------------------
  66. CTFWeaponBaseGrenadeProj::CTFWeaponBaseGrenadeProj()
  67. {
  68. #ifndef CLIENT_DLL
  69. m_bUseImpactNormal = false;
  70. m_vecImpactNormal.Init();
  71. m_iDeflected = 0;
  72. m_flDestroyableTime = 0.0f;
  73. m_bIsMerasmusGrenade = false;
  74. m_iDestroyableHitCount = 0;
  75. #endif
  76. }
  77. //-----------------------------------------------------------------------------
  78. // Purpose: Destructor.
  79. //-----------------------------------------------------------------------------
  80. CTFWeaponBaseGrenadeProj::~CTFWeaponBaseGrenadeProj()
  81. {
  82. }
  83. //-----------------------------------------------------------------------------
  84. // Purpose:
  85. //-----------------------------------------------------------------------------
  86. int CTFWeaponBaseGrenadeProj::GetDamageType()
  87. {
  88. int iDmgType = g_aWeaponDamageTypes[ GetWeaponID() ];
  89. if ( m_bCritical )
  90. {
  91. iDmgType |= DMG_CRITICAL;
  92. }
  93. return iDmgType;
  94. }
  95. //-----------------------------------------------------------------------------
  96. // Purpose:
  97. //-----------------------------------------------------------------------------
  98. int CTFWeaponBaseGrenadeProj::GetDamageCustom()
  99. {
  100. return 0;
  101. }
  102. const float GRENADE_COEFFICIENT_OF_RESTITUTION = 0.2f;
  103. //-----------------------------------------------------------------------------
  104. // Purpose: Bounce backwards
  105. //-----------------------------------------------------------------------------
  106. void CTFWeaponBaseGrenadeProj::BounceOff( IPhysicsObject *pPhysics )
  107. {
  108. if ( !pPhysics )
  109. return;
  110. Vector vecVel;
  111. pPhysics->GetVelocity( &vecVel, NULL );
  112. vecVel *= -GRENADE_COEFFICIENT_OF_RESTITUTION;
  113. pPhysics->SetVelocity( &vecVel, NULL );
  114. }
  115. //-----------------------------------------------------------------------------
  116. // Purpose:
  117. //-----------------------------------------------------------------------------
  118. float CTFWeaponBaseGrenadeProj::GetDamageRadius()
  119. {
  120. float flRadius = m_DmgRadius;
  121. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_hLauncher, flRadius, mult_explosion_radius );
  122. return flRadius;
  123. }
  124. //-----------------------------------------------------------------------------
  125. // Purpose:
  126. //-----------------------------------------------------------------------------
  127. void CTFWeaponBaseGrenadeProj::Precache( void )
  128. {
  129. BaseClass::Precache();
  130. #ifndef CLIENT_DLL
  131. PrecacheModel( NOGRENADE_SPRITE );
  132. PrecacheParticleSystem( "critical_grenade_blue" );
  133. PrecacheParticleSystem( "critical_grenade_red" );
  134. #endif
  135. }
  136. //=============================================================================
  137. //
  138. // Client specific functions.
  139. //
  140. #ifdef CLIENT_DLL
  141. //-----------------------------------------------------------------------------
  142. // Purpose:
  143. //-----------------------------------------------------------------------------
  144. void CTFWeaponBaseGrenadeProj::Spawn()
  145. {
  146. m_flSpawnTime = gpGlobals->curtime;
  147. BaseClass::Spawn();
  148. AddFlag( FL_GRENADE );
  149. }
  150. //-----------------------------------------------------------------------------
  151. // Purpose:
  152. //-----------------------------------------------------------------------------
  153. void CTFWeaponBaseGrenadeProj::OnDataChanged( DataUpdateType_t type )
  154. {
  155. BaseClass::OnDataChanged( type );
  156. if ( type == DATA_UPDATE_CREATED )
  157. {
  158. // Now stick our initial velocity into the interpolation history
  159. CInterpolatedVar< Vector > &interpolator = GetOriginInterpolator();
  160. interpolator.ClearHistory();
  161. float changeTime = GetLastChangeTime( LATCH_SIMULATION_VAR );
  162. // Add a sample 1 second back.
  163. Vector vCurOrigin = GetLocalOrigin() - m_vInitialVelocity;
  164. interpolator.AddToHead( changeTime - 1.0, &vCurOrigin, false );
  165. // Add the current sample.
  166. vCurOrigin = GetLocalOrigin();
  167. interpolator.AddToHead( changeTime, &vCurOrigin, false );
  168. }
  169. }
  170. //=============================================================================
  171. //
  172. // Server specific functions.
  173. //
  174. #else
  175. //-----------------------------------------------------------------------------
  176. // Purpose:
  177. //-----------------------------------------------------------------------------
  178. CTFWeaponBaseGrenadeProj *CTFWeaponBaseGrenadeProj::Create( const char *szName, const Vector &position, const QAngle &angles,
  179. const Vector &velocity, const AngularImpulse &angVelocity,
  180. CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo, int iFlags )
  181. {
  182. CTFWeaponBaseGrenadeProj *pGrenade = static_cast<CTFWeaponBaseGrenadeProj*>( CBaseEntity::Create( szName, position, angles, pOwner ) );
  183. if ( pGrenade )
  184. {
  185. pGrenade->InitGrenade( velocity, angVelocity, pOwner, weaponInfo );
  186. }
  187. return pGrenade;
  188. }
  189. //-----------------------------------------------------------------------------
  190. // Purpose:
  191. //-----------------------------------------------------------------------------
  192. void CTFWeaponBaseGrenadeProj::InitGrenade( const Vector &velocity, const AngularImpulse &angVelocity,
  193. CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo )
  194. {
  195. InitGrenade( velocity, angVelocity, pOwner, weaponInfo.GetWeaponData( TF_WEAPON_PRIMARY_MODE ).m_nDamage, weaponInfo.m_flDamageRadius );
  196. }
  197. //-----------------------------------------------------------------------------
  198. // Purpose:
  199. //-----------------------------------------------------------------------------
  200. void CTFWeaponBaseGrenadeProj::InitGrenade( const Vector &velocity, const AngularImpulse &angVelocity,
  201. CBaseCombatCharacter *pOwner, const int iDamage, const float flRadius )
  202. {
  203. // We can't use OwnerEntity for grenades, because then the owner can't shoot them with his hitscan weapons (due to collide rules)
  204. // Thrower is used to store the person who threw the grenade, for damage purposes.
  205. SetOwnerEntity( NULL );
  206. SetThrower( pOwner );
  207. SetupInitialTransmittedGrenadeVelocity( velocity );
  208. SetGravity( 0.4f/*BaseClass::GetGrenadeGravity()*/ );
  209. SetFriction( 0.2f/*BaseClass::GetGrenadeFriction()*/ );
  210. SetElasticity( 0.45f/*BaseClass::GetGrenadeElasticity()*/ );
  211. SetDamage( iDamage );
  212. SetDamageRadius( flRadius );
  213. ChangeTeam( pOwner ? pOwner->GetTeamNumber() : TEAM_UNASSIGNED );
  214. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  215. if ( pPhysicsObject )
  216. {
  217. pPhysicsObject->AddVelocity( &velocity, &angVelocity );
  218. }
  219. }
  220. //-----------------------------------------------------------------------------
  221. // Purpose:
  222. //-----------------------------------------------------------------------------
  223. void CTFWeaponBaseGrenadeProj::Spawn( void )
  224. {
  225. // Base class spawn.
  226. BaseClass::Spawn();
  227. // So it will collide with physics props!
  228. SetSolidFlags( FSOLID_NOT_STANDABLE );
  229. SetSolid( SOLID_BBOX );
  230. AddEffects( EF_NOSHADOW );
  231. // Set the grenade size here.
  232. UTIL_SetSize( this, Vector( -2.0f, -2.0f, -2.0f ), Vector( 2.0f, 2.0f, 2.0f ) );
  233. // Set the movement type.
  234. SetCollisionGroup( TF_COLLISIONGROUP_GRENADES );
  235. VPhysicsInitNormal( SOLID_BBOX, 0, false );
  236. m_takedamage = DAMAGE_EVENTS_ONLY;
  237. // Set the team.
  238. ChangeTeam( GetThrower() ? GetThrower()->GetTeamNumber() : TEAM_UNASSIGNED );
  239. // Set skin based on team ( red = 1, blue = 2 )
  240. m_nSkin = ( GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0;
  241. m_flDestroyableTime = gpGlobals->curtime + TF_GRENADE_DESTROYABLE_TIMER;
  242. // Setup the think and touch functions (see CBaseEntity).
  243. SetThink( &CTFWeaponBaseGrenadeProj::DetonateThink );
  244. SetNextThink( gpGlobals->curtime + 0.2 );
  245. }
  246. //-----------------------------------------------------------------------------
  247. // Purpose:
  248. //-----------------------------------------------------------------------------
  249. #define TF_GRENADE_JUMP_RADIUS 146
  250. void CTFWeaponBaseGrenadeProj::Explode( trace_t *pTrace, int bitsDamageType )
  251. {
  252. if ( ShouldNotDetonate() )
  253. {
  254. Destroy();
  255. return;
  256. }
  257. SetModelName( NULL_STRING );//invisible
  258. AddSolidFlags( FSOLID_NOT_SOLID );
  259. m_takedamage = DAMAGE_NO;
  260. // Pull out of the wall a bit
  261. if ( pTrace->fraction != 1.0 )
  262. {
  263. SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) );
  264. }
  265. CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), BASEGRENADE_EXPLOSION_VOLUME, 3.0 );
  266. // Explosion effect on client
  267. Vector vecOrigin = GetAbsOrigin();
  268. CPVSFilter filter( vecOrigin );
  269. item_definition_index_t ownerWeaponDefIndex = INVALID_ITEM_DEF_INDEX;
  270. CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>(GetOriginalLauncher());
  271. if (pWeapon)
  272. {
  273. ownerWeaponDefIndex = pWeapon->GetAttributeContainer()->GetItem()->GetItemDefIndex();
  274. }
  275. // Halloween Custom Spell Effect
  276. int iHalloweenSpell = 0;
  277. int iCustomParticleIndex = GetCustomParticleIndex();
  278. if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
  279. {
  280. CALL_ATTRIB_HOOK_INT_ON_OTHER( m_hLauncher, iHalloweenSpell, halloween_pumpkin_explosions );
  281. if ( iHalloweenSpell > 0 )
  282. {
  283. iCustomParticleIndex = GetParticleSystemIndex( "halloween_explosion" );
  284. }
  285. }
  286. int iLargeExplosion = 0;
  287. CALL_ATTRIB_HOOK_INT_ON_OTHER( m_hLauncher, iLargeExplosion, use_large_smoke_explosion );
  288. if ( iLargeExplosion > 0 )
  289. {
  290. DispatchParticleEffect( "explosionTrail_seeds_mvm", GetAbsOrigin(), GetAbsAngles() );
  291. DispatchParticleEffect( "fluidSmokeExpl_ring_mvm", GetAbsOrigin(), GetAbsAngles() );
  292. }
  293. if ( UseImpactNormal() )
  294. {
  295. if ( pTrace->m_pEnt && pTrace->m_pEnt->IsPlayer() )
  296. {
  297. TE_TFExplosion(filter, 0.0f, vecOrigin, GetImpactNormal(), GetWeaponID(), pTrace->m_pEnt->entindex(), ownerWeaponDefIndex, SPECIAL1, iCustomParticleIndex);
  298. }
  299. else
  300. {
  301. TE_TFExplosion(filter, 0.0f, vecOrigin, GetImpactNormal(), GetWeaponID(), kInvalidEHandleExplosion, ownerWeaponDefIndex, SPECIAL1, iCustomParticleIndex);
  302. }
  303. }
  304. else
  305. {
  306. if ( pTrace->m_pEnt && pTrace->m_pEnt->IsPlayer() )
  307. {
  308. TE_TFExplosion(filter, 0.0f, vecOrigin, pTrace->plane.normal, GetWeaponID(), pTrace->m_pEnt->entindex(), ownerWeaponDefIndex, SPECIAL1, iCustomParticleIndex);
  309. }
  310. else
  311. {
  312. TE_TFExplosion(filter, 0.0f, vecOrigin, pTrace->plane.normal, GetWeaponID(), kInvalidEHandleExplosion, ownerWeaponDefIndex, SPECIAL1, iCustomParticleIndex);
  313. }
  314. }
  315. // Use the thrower's position as the reported position
  316. Vector vecReported = GetThrower() ? GetThrower()->GetAbsOrigin() : vec3_origin;
  317. int nCustomDamage = GetDamageCustom();
  318. CTakeDamageInfo info( this, GetThrower(), m_hLauncher, GetBlastForce(), GetAbsOrigin(), m_flDamage, bitsDamageType, nCustomDamage, &vecReported );
  319. float flRadius = GetDamageRadius();
  320. #ifdef STAGING_ONLY
  321. if ( tf_grenade_show_radius.GetBool() )
  322. {
  323. DrawRadius( flRadius );
  324. }
  325. #endif
  326. CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, flRadius, NULL, TF_GRENADE_JUMP_RADIUS );
  327. TFGameRules()->RadiusDamage( radiusinfo );
  328. // Don't decal players with scorch.
  329. if ( pTrace->m_pEnt && !pTrace->m_pEnt->IsPlayer() )
  330. {
  331. UTIL_DecalTrace( pTrace, "Scorch" );
  332. }
  333. if ( pTrace->m_pEnt && pTrace->m_pEnt->IsPlayer() && GetThrower() )
  334. {
  335. CTFPlayer *pTarget = ToTFPlayer( GetEnemy() );
  336. if ( pTarget )
  337. {
  338. if ( pTarget->GetTeamNumber() != GetThrower()->GetTeamNumber() )
  339. {
  340. IGameEvent *event = gameeventmanager->CreateEvent( "projectile_direct_hit" );
  341. if ( event )
  342. {
  343. event->SetInt( "attacker", GetThrower()->entindex() );
  344. event->SetInt( "victim", pTarget->entindex() );
  345. gameeventmanager->FireEvent( event, true );
  346. }
  347. }
  348. }
  349. }
  350. SetThink( &CBaseGrenade::SUB_Remove );
  351. SetTouch( NULL );
  352. AddEffects( EF_NODRAW );
  353. SetAbsVelocity( vec3_origin );
  354. SetNextThink( gpGlobals->curtime );
  355. }
  356. //-----------------------------------------------------------------------------
  357. // Purpose:
  358. //-----------------------------------------------------------------------------
  359. int CTFWeaponBaseGrenadeProj::OnTakeDamage( const CTakeDamageInfo &info )
  360. {
  361. CTakeDamageInfo info2 = info;
  362. // Reduce explosion damage so that we don't get knocked too far
  363. if ( info.GetDamageType() & DMG_BLAST )
  364. {
  365. info2.ScaleDamageForce( 0.05 );
  366. }
  367. // We need to skip back to the base entity take damage, because
  368. // CBaseCombatCharacter doesn't, which prevents us from reacting
  369. // to physics impact damage.
  370. return CBaseEntity::OnTakeDamage( info2 );
  371. }
  372. //-----------------------------------------------------------------------------
  373. // Purpose:
  374. //-----------------------------------------------------------------------------
  375. void CTFWeaponBaseGrenadeProj::DetonateThink( void )
  376. {
  377. if ( !IsInWorld() )
  378. {
  379. Remove( );
  380. return;
  381. }
  382. if ( gpGlobals->curtime > m_flDetonateTime )
  383. {
  384. Detonate();
  385. return;
  386. }
  387. SetNextThink( gpGlobals->curtime + 0.2 );
  388. }
  389. //-----------------------------------------------------------------------------
  390. // Purpose:
  391. //-----------------------------------------------------------------------------
  392. void CTFWeaponBaseGrenadeProj::Detonate( void )
  393. {
  394. trace_t tr;
  395. Vector vecSpot;// trace starts here!
  396. SetThink( NULL );
  397. vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );
  398. UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -32 ), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, & tr);
  399. Explode( &tr, GetDamageType() );
  400. if ( GetShakeAmplitude() )
  401. {
  402. UTIL_ScreenShake( GetAbsOrigin(), GetShakeAmplitude(), 150.0, 1.0, GetShakeRadius(), SHAKE_START );
  403. }
  404. }
  405. //-----------------------------------------------------------------------------
  406. // Purpose: Sets the time at which the grenade will explode.
  407. //-----------------------------------------------------------------------------
  408. void CTFWeaponBaseGrenadeProj::SetDetonateTimerLength( float timer )
  409. {
  410. float fFuseMult = 1.0f;
  411. if ( GetOwnerEntity() )
  412. {
  413. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), fFuseMult, fuse_mult );
  414. }
  415. m_flDetonateTime = gpGlobals->curtime + ( timer * fFuseMult );
  416. }
  417. //-----------------------------------------------------------------------------
  418. // Purpose:
  419. //-----------------------------------------------------------------------------
  420. void CTFWeaponBaseGrenadeProj::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity )
  421. {
  422. //Assume all surfaces have the same elasticity
  423. float flSurfaceElasticity = 1.0;
  424. //Don't bounce off of players with perfect elasticity
  425. if( trace.m_pEnt && trace.m_pEnt->IsPlayer() )
  426. {
  427. flSurfaceElasticity = 0.3;
  428. }
  429. #if 0
  430. // if its breakable glass and we kill it, don't bounce.
  431. // give some damage to the glass, and if it breaks, pass
  432. // through it.
  433. bool breakthrough = false;
  434. if( trace.m_pEnt && FClassnameIs( trace.m_pEnt, "func_breakable" ) )
  435. {
  436. breakthrough = true;
  437. }
  438. if( trace.m_pEnt && FClassnameIs( trace.m_pEnt, "func_breakable_surf" ) )
  439. {
  440. breakthrough = true;
  441. }
  442. if (breakthrough)
  443. {
  444. CTakeDamageInfo info( this, this, 10, DMG_CLUB );
  445. trace.m_pEnt->DispatchTraceAttack( info, GetAbsVelocity(), &trace );
  446. ApplyMultiDamage();
  447. if( trace.m_pEnt->m_iHealth <= 0 )
  448. {
  449. // slow our flight a little bit
  450. Vector vel = GetAbsVelocity();
  451. vel *= 0.4;
  452. SetAbsVelocity( vel );
  453. return;
  454. }
  455. }
  456. #endif
  457. float flTotalElasticity = GetElasticity() * flSurfaceElasticity;
  458. flTotalElasticity = clamp( flTotalElasticity, 0.0f, 0.9f );
  459. // NOTE: A backoff of 2.0f is a reflection
  460. Vector vecAbsVelocity;
  461. PhysicsClipVelocity( GetAbsVelocity(), trace.plane.normal, vecAbsVelocity, 2.0f );
  462. vecAbsVelocity *= flTotalElasticity;
  463. // Get the total velocity (player + conveyors, etc.)
  464. VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity );
  465. float flSpeedSqr = DotProduct( vecVelocity, vecVelocity );
  466. // Stop if on ground.
  467. if ( trace.plane.normal.z > 0.7f ) // Floor
  468. {
  469. // Verify that we have an entity.
  470. CBaseEntity *pEntity = trace.m_pEnt;
  471. Assert( pEntity );
  472. SetAbsVelocity( vecAbsVelocity );
  473. if ( flSpeedSqr < ( 30 * 30 ) )
  474. {
  475. if ( pEntity->IsStandable() )
  476. {
  477. SetGroundEntity( pEntity );
  478. }
  479. // Reset velocities.
  480. SetAbsVelocity( vec3_origin );
  481. SetLocalAngularVelocity( vec3_angle );
  482. //align to the ground so we're not standing on end
  483. QAngle angle;
  484. VectorAngles( trace.plane.normal, angle );
  485. // rotate randomly in yaw
  486. angle[1] = random->RandomFloat( 0, 360 );
  487. // TFTODO: rotate around trace.plane.normal
  488. SetAbsAngles( angle );
  489. }
  490. else
  491. {
  492. Vector vecDelta = GetBaseVelocity() - vecAbsVelocity;
  493. Vector vecBaseDir = GetBaseVelocity();
  494. VectorNormalize( vecBaseDir );
  495. float flScale = vecDelta.Dot( vecBaseDir );
  496. VectorScale( vecAbsVelocity, ( 1.0f - trace.fraction ) * gpGlobals->frametime, vecVelocity );
  497. VectorMA( vecVelocity, ( 1.0f - trace.fraction ) * gpGlobals->frametime, GetBaseVelocity() * flScale, vecVelocity );
  498. PhysicsPushEntity( vecVelocity, &trace );
  499. }
  500. }
  501. else
  502. {
  503. // If we get *too* slow, we'll stick without ever coming to rest because
  504. // we'll get pushed down by gravity faster than we can escape from the wall.
  505. if ( flSpeedSqr < ( 30 * 30 ) )
  506. {
  507. // Reset velocities.
  508. SetAbsVelocity( vec3_origin );
  509. SetLocalAngularVelocity( vec3_angle );
  510. }
  511. else
  512. {
  513. SetAbsVelocity( vecAbsVelocity );
  514. }
  515. }
  516. BounceSound();
  517. #if 0
  518. // tell the bots a grenade has bounced
  519. CCSPlayer *player = ToCSPlayer(GetThrower());
  520. if ( player )
  521. {
  522. KeyValues *event = new KeyValues( "grenade_bounce" );
  523. event->SetInt( "userid", player->GetUserID() );
  524. gameeventmanager->FireEventServerOnly( event );
  525. }
  526. #endif
  527. }
  528. bool CTFWeaponBaseGrenadeProj::ShouldNotDetonate( void )
  529. {
  530. return InNoGrenadeZone( this );
  531. }
  532. void CTFWeaponBaseGrenadeProj::Destroy( bool bBlinkOut, bool bBreak )
  533. {
  534. if ( bBreak )
  535. {
  536. CPVSFilter filter( GetAbsOrigin() );
  537. UserMessageBegin( filter, "BreakModelRocketDud" );
  538. WRITE_SHORT( GetModelIndex() );
  539. WRITE_VEC3COORD( GetAbsOrigin() );
  540. WRITE_ANGLES( GetAbsAngles() );
  541. MessageEnd();
  542. }
  543. // Kill it
  544. SetThink( &BaseClass::SUB_Remove );
  545. SetNextThink( gpGlobals->curtime );
  546. SetTouch( NULL );
  547. AddEffects( EF_NODRAW );
  548. if ( bBlinkOut )
  549. {
  550. // Sprite flash
  551. CSprite *pGlowSprite = CSprite::SpriteCreate( NOGRENADE_SPRITE, GetAbsOrigin(), false );
  552. if ( pGlowSprite )
  553. {
  554. pGlowSprite->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxFadeFast );
  555. pGlowSprite->SetThink( &CSprite::SUB_Remove );
  556. pGlowSprite->SetNextThink( gpGlobals->curtime + 1.0 );
  557. }
  558. }
  559. }
  560. //-----------------------------------------------------------------------------
  561. // Purpose: This will hit only things that are in newCollisionGroup, but NOT in collisionGroupAlreadyChecked
  562. // Always ignores other grenade projectiles.
  563. //-----------------------------------------------------------------------------
  564. class CTraceFilterCollisionGrenades : public CTraceFilterEntitiesOnly
  565. {
  566. public:
  567. // It does have a base, but we'll never network anything below here..
  568. DECLARE_CLASS_NOBASE( CTraceFilterCollisionGrenades );
  569. CTraceFilterCollisionGrenades( const IHandleEntity *passentity, const IHandleEntity *passentity2 )
  570. : m_pPassEnt(passentity), m_pPassEnt2(passentity2)
  571. {
  572. }
  573. virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  574. {
  575. if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
  576. return false;
  577. CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
  578. if ( pEntity )
  579. {
  580. if ( pEntity == m_pPassEnt2 )
  581. return false;
  582. if ( pEntity->GetCollisionGroup() == TF_COLLISIONGROUP_GRENADES )
  583. return false;
  584. if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_ROCKETS )
  585. return false;
  586. if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
  587. return false;
  588. if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_RESPAWNROOMS )
  589. return false;
  590. if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_NONE )
  591. return false;
  592. return true;
  593. }
  594. return true;
  595. }
  596. protected:
  597. const IHandleEntity *m_pPassEnt;
  598. const IHandleEntity *m_pPassEnt2;
  599. };
  600. //-----------------------------------------------------------------------------
  601. // Purpose: Grenades aren't solid to players, so players don't get stuck on
  602. // them when they're lying on the ground. We still want thrown grenades
  603. // to bounce of players though, so manually trace ahead and see if we'd
  604. // hit something that we'd like the grenade to "collide" with.
  605. //-----------------------------------------------------------------------------
  606. void CTFWeaponBaseGrenadeProj::VPhysicsUpdate( IPhysicsObject *pPhysics )
  607. {
  608. BaseClass::VPhysicsUpdate( pPhysics );
  609. Vector vel;
  610. AngularImpulse angVel;
  611. pPhysics->GetVelocity( &vel, &angVel );
  612. Vector start = GetAbsOrigin();
  613. // find all entities that my collision group wouldn't hit, but COLLISION_GROUP_NONE would and bounce off of them as a ray cast
  614. CTraceFilterCollisionGrenades filter( this, GetThrower() );
  615. ITraceFilter *pFilterChain = NULL;
  616. CTraceFilterIgnoreFriendlyCombatItems filterCombatItems( this, COLLISION_GROUP_NONE, GetTeamNumber(), true );
  617. if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
  618. {
  619. pFilterChain = &filterCombatItems;
  620. }
  621. CTraceFilterChain filterChain( &filter, pFilterChain );
  622. trace_t tr;
  623. UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filterChain, &tr );
  624. bool bHitEnemy = tr.m_pEnt && tr.m_pEnt->GetTeamNumber() == GetEnemyTeam( GetTeamNumber() );
  625. bool bHitFriendly = tr.m_pEnt && tr.m_pEnt->GetTeamNumber() == GetTeamNumber() && CanCollideWithTeammates();
  626. // Combat items are solid to enemy projectiles and bullets
  627. if ( bHitEnemy && tr.m_pEnt->IsCombatItem() )
  628. {
  629. if ( IsAllowedToExplode() )
  630. {
  631. Explode( &tr, GetDamageType() );
  632. }
  633. else
  634. {
  635. BounceOff( pPhysics );
  636. }
  637. return;
  638. }
  639. if ( tr.startsolid )
  640. {
  641. if ( bHitEnemy )
  642. {
  643. Touch( tr.m_pEnt );
  644. }
  645. else if ( !m_bInSolid && bHitFriendly )
  646. {
  647. BounceOff( pPhysics );
  648. }
  649. m_bInSolid = true;
  650. return;
  651. }
  652. m_bInSolid = false;
  653. if ( tr.DidHit() )
  654. {
  655. Touch( tr.m_pEnt );
  656. if ( bHitFriendly || bHitEnemy )
  657. {
  658. // reflect velocity around normal
  659. vel = -2.0f * tr.plane.normal * DotProduct(vel,tr.plane.normal) + vel;
  660. // absorb 80% in impact
  661. vel *= GetElasticity();
  662. if ( bHitEnemy == true )
  663. {
  664. vel *= 0.5f;
  665. }
  666. angVel *= -0.5f;
  667. pPhysics->SetVelocity( &vel, &angVel );
  668. }
  669. }
  670. }
  671. #ifdef STAGING_ONLY
  672. void CTFWeaponBaseGrenadeProj::DrawRadius( float flRadius )
  673. {
  674. Vector pos = GetAbsOrigin();
  675. int r = 255;
  676. int g = 0, b = 0;
  677. float flLifetime = tf_grenade_show_radius_time.GetFloat();
  678. bool bDepthTest = true;
  679. Vector edge, lastEdge;
  680. NDebugOverlay::Line( pos, pos + Vector( 0, 0, 50 ), r, g, b, !bDepthTest, flLifetime );
  681. lastEdge = Vector( flRadius + pos.x, pos.y, pos.z );
  682. float angle;
  683. for( angle=0.0f; angle <= 360.0f; angle += 22.5f )
  684. {
  685. edge.x = flRadius * cos( DEG2RAD( angle ) ) + pos.x;
  686. edge.y = pos.y;
  687. edge.z = flRadius * sin( DEG2RAD( angle ) ) + pos.z;
  688. NDebugOverlay::Line( edge, lastEdge, r, g, b, !bDepthTest, flLifetime );
  689. lastEdge = edge;
  690. }
  691. lastEdge = Vector( pos.x, flRadius + pos.y, pos.z );
  692. for( angle=0.0f; angle <= 360.0f; angle += 22.5f )
  693. {
  694. edge.x = pos.x;
  695. edge.y = flRadius * cos( DEG2RAD( angle ) ) + pos.y;
  696. edge.z = flRadius * sin( DEG2RAD( angle ) ) + pos.z;
  697. NDebugOverlay::Line( edge, lastEdge, r, g, b, !bDepthTest, flLifetime );
  698. lastEdge = edge;
  699. }
  700. lastEdge = Vector( pos.x, flRadius + pos.y, pos.z );
  701. for( angle=0.0f; angle <= 360.0f; angle += 22.5f )
  702. {
  703. edge.x = flRadius * cos( DEG2RAD( angle ) ) + pos.x;
  704. edge.y = flRadius * sin( DEG2RAD( angle ) ) + pos.y;
  705. edge.z = pos.z;
  706. NDebugOverlay::Line( edge, lastEdge, r, g, b, !bDepthTest, flLifetime );
  707. lastEdge = edge;
  708. }
  709. }
  710. #endif // STAGING_ONLY
  711. #endif