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.

558 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "decals.h"
  9. #include "basegrenade_shared.h"
  10. #include "shake.h"
  11. #include "engine/IEngineSound.h"
  12. #if !defined( CLIENT_DLL )
  13. #include "soundent.h"
  14. #include "entitylist.h"
  15. #include "gamestats.h"
  16. #endif
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. extern short g_sModelIndexFireball; // (in combatweapon.cpp) holds the index for the fireball
  20. extern short g_sModelIndexWExplosion; // (in combatweapon.cpp) holds the index for the underwater explosion
  21. extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud
  22. extern ConVar sk_plr_dmg_grenade;
  23. #if !defined( CLIENT_DLL )
  24. // Global Savedata for friction modifier
  25. BEGIN_DATADESC( CBaseGrenade )
  26. // nextGrenade
  27. DEFINE_FIELD( m_hThrower, FIELD_EHANDLE ),
  28. // m_fRegisteredSound ???
  29. DEFINE_FIELD( m_bIsLive, FIELD_BOOLEAN ),
  30. DEFINE_FIELD( m_DmgRadius, FIELD_FLOAT ),
  31. DEFINE_FIELD( m_flDetonateTime, FIELD_TIME ),
  32. DEFINE_FIELD( m_flWarnAITime, FIELD_TIME ),
  33. DEFINE_FIELD( m_flDamage, FIELD_FLOAT ),
  34. DEFINE_FIELD( m_iszBounceSound, FIELD_STRING ),
  35. DEFINE_FIELD( m_bHasWarnedAI, FIELD_BOOLEAN ),
  36. // Function Pointers
  37. DEFINE_THINKFUNC( Smoke ),
  38. DEFINE_ENTITYFUNC( BounceTouch ),
  39. DEFINE_ENTITYFUNC( SlideTouch ),
  40. DEFINE_ENTITYFUNC( ExplodeTouch ),
  41. DEFINE_USEFUNC( DetonateUse ),
  42. DEFINE_THINKFUNC( DangerSoundThink ),
  43. DEFINE_THINKFUNC( PreDetonate ),
  44. DEFINE_THINKFUNC( Detonate ),
  45. DEFINE_THINKFUNC( TumbleThink ),
  46. END_DATADESC()
  47. void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID);
  48. #endif
  49. IMPLEMENT_NETWORKCLASS_ALIASED( BaseGrenade, DT_BaseGrenade )
  50. BEGIN_NETWORK_TABLE( CBaseGrenade, DT_BaseGrenade )
  51. #if !defined( CLIENT_DLL )
  52. SendPropFloat( SENDINFO( m_flDamage ), 10, SPROP_ROUNDDOWN, 0.0, 256.0f ),
  53. SendPropFloat( SENDINFO( m_DmgRadius ), 10, SPROP_ROUNDDOWN, 0.0, 1024.0f ),
  54. SendPropInt( SENDINFO( m_bIsLive ), 1, SPROP_UNSIGNED ),
  55. // SendPropTime( SENDINFO( m_flDetonateTime ) ),
  56. SendPropEHandle( SENDINFO( m_hThrower ) ),
  57. SendPropVector( SENDINFO( m_vecVelocity ), 0, SPROP_NOSCALE ),
  58. // HACK: Use same flag bits as player for now
  59. SendPropInt ( SENDINFO(m_fFlags), PLAYER_FLAG_BITS, SPROP_UNSIGNED, SendProxy_CropFlagsToPlayerFlagBitsLength ),
  60. #else
  61. RecvPropFloat( RECVINFO( m_flDamage ) ),
  62. RecvPropFloat( RECVINFO( m_DmgRadius ) ),
  63. RecvPropInt( RECVINFO( m_bIsLive ) ),
  64. // RecvPropTime( RECVINFO( m_flDetonateTime ) ),
  65. RecvPropEHandle( RECVINFO( m_hThrower ) ),
  66. // Need velocity from grenades to make animation system work correctly when running
  67. RecvPropVector( RECVINFO(m_vecVelocity), 0, RecvProxy_LocalVelocity ),
  68. RecvPropInt( RECVINFO( m_fFlags ) ),
  69. #endif
  70. END_NETWORK_TABLE()
  71. LINK_ENTITY_TO_CLASS( grenade, CBaseGrenade );
  72. #if defined( CLIENT_DLL )
  73. BEGIN_PREDICTION_DATA( CBaseGrenade )
  74. DEFINE_PRED_FIELD( m_hThrower, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
  75. DEFINE_PRED_FIELD( m_bIsLive, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
  76. DEFINE_PRED_FIELD( m_DmgRadius, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
  77. // DEFINE_PRED_FIELD_TOL( m_flDetonateTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ),
  78. DEFINE_PRED_FIELD( m_flDamage, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
  79. DEFINE_PRED_FIELD_TOL( m_vecVelocity, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.5f ),
  80. DEFINE_PRED_FIELD_TOL( m_flNextAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ),
  81. // DEFINE_FIELD( m_fRegisteredSound, FIELD_BOOLEAN ),
  82. // DEFINE_FIELD( m_iszBounceSound, FIELD_STRING ),
  83. END_PREDICTION_DATA()
  84. #endif
  85. // Grenades flagged with this will be triggered when the owner calls detonateSatchelCharges
  86. #define SF_DETONATE 0x0001
  87. // UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution.
  88. void CBaseGrenade::Explode( trace_t *pTrace, int bitsDamageType )
  89. {
  90. #if !defined( CLIENT_DLL )
  91. SetModelName( NULL_STRING );//invisible
  92. AddSolidFlags( FSOLID_NOT_SOLID );
  93. m_takedamage = DAMAGE_NO;
  94. // Pull out of the wall a bit
  95. if ( pTrace->fraction != 1.0 )
  96. {
  97. SetAbsOrigin( pTrace->endpos + (pTrace->plane.normal * 0.6) );
  98. }
  99. Vector vecAbsOrigin = GetAbsOrigin();
  100. int contents = UTIL_PointContents ( vecAbsOrigin );
  101. #if defined( TF_DLL )
  102. // Since this code only runs on the server, make sure it shows the tempents it creates.
  103. // This solves a problem with remote detonating the pipebombs (client wasn't seeing the explosion effect)
  104. CDisablePredictionFiltering disabler;
  105. #endif
  106. if ( pTrace->fraction != 1.0 )
  107. {
  108. Vector vecNormal = pTrace->plane.normal;
  109. surfacedata_t *pdata = physprops->GetSurfaceData( pTrace->surface.surfaceProps );
  110. CPASFilter filter( vecAbsOrigin );
  111. te->Explosion( filter, -1.0, // don't apply cl_interp delay
  112. &vecAbsOrigin,
  113. !( contents & MASK_WATER ) ? g_sModelIndexFireball : g_sModelIndexWExplosion,
  114. m_DmgRadius * .03,
  115. 25,
  116. TE_EXPLFLAG_NONE,
  117. m_DmgRadius,
  118. m_flDamage,
  119. &vecNormal,
  120. (char) pdata->game.material );
  121. }
  122. else
  123. {
  124. CPASFilter filter( vecAbsOrigin );
  125. te->Explosion( filter, -1.0, // don't apply cl_interp delay
  126. &vecAbsOrigin,
  127. !( contents & MASK_WATER ) ? g_sModelIndexFireball : g_sModelIndexWExplosion,
  128. m_DmgRadius * .03,
  129. 25,
  130. TE_EXPLFLAG_NONE,
  131. m_DmgRadius,
  132. m_flDamage );
  133. }
  134. #if !defined( CLIENT_DLL )
  135. CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), BASEGRENADE_EXPLOSION_VOLUME, 3.0 );
  136. #endif
  137. // Use the thrower's position as the reported position
  138. Vector vecReported = m_hThrower ? m_hThrower->GetAbsOrigin() : vec3_origin;
  139. CTakeDamageInfo info( this, m_hThrower, GetBlastForce(), GetAbsOrigin(), m_flDamage, bitsDamageType, 0, &vecReported );
  140. RadiusDamage( info, GetAbsOrigin(), m_DmgRadius, CLASS_NONE, NULL );
  141. UTIL_DecalTrace( pTrace, "Scorch" );
  142. EmitSound( "BaseGrenade.Explode" );
  143. SetThink( &CBaseGrenade::SUB_Remove );
  144. SetTouch( NULL );
  145. SetSolid( SOLID_NONE );
  146. AddEffects( EF_NODRAW );
  147. SetAbsVelocity( vec3_origin );
  148. #if HL2_EPISODIC
  149. // Because the grenade is zipped out of the world instantly, the EXPLOSION sound that it makes for
  150. // the AI is also immediately destroyed. For this reason, we now make the grenade entity inert and
  151. // throw it away in 1/10th of a second instead of right away. Removing the grenade instantly causes
  152. // intermittent bugs with env_microphones who are listening for explosions. They will 'randomly' not
  153. // hear explosion sounds when the grenade is removed and the SoundEnt thinks (and removes the sound)
  154. // before the env_microphone thinks and hears the sound.
  155. SetNextThink( gpGlobals->curtime + 0.1 );
  156. #else
  157. SetNextThink( gpGlobals->curtime );
  158. #endif//HL2_EPISODIC
  159. #if defined( HL2_DLL )
  160. CBasePlayer *pPlayer = ToBasePlayer( m_hThrower.Get() );
  161. if ( pPlayer )
  162. {
  163. gamestats->Event_WeaponHit( pPlayer, true, "weapon_frag", info );
  164. }
  165. #endif
  166. #endif
  167. }
  168. void CBaseGrenade::Smoke( void )
  169. {
  170. Vector vecAbsOrigin = GetAbsOrigin();
  171. if ( UTIL_PointContents ( vecAbsOrigin ) & MASK_WATER )
  172. {
  173. UTIL_Bubbles( vecAbsOrigin - Vector( 64, 64, 64 ), vecAbsOrigin + Vector( 64, 64, 64 ), 100 );
  174. }
  175. else
  176. {
  177. CPVSFilter filter( vecAbsOrigin );
  178. te->Smoke( filter, 0.0,
  179. &vecAbsOrigin, g_sModelIndexSmoke,
  180. m_DmgRadius * 0.03,
  181. 24 );
  182. }
  183. #if !defined( CLIENT_DLL )
  184. SetThink ( &CBaseGrenade::SUB_Remove );
  185. #endif
  186. SetNextThink( gpGlobals->curtime );
  187. }
  188. void CBaseGrenade::Event_Killed( const CTakeDamageInfo &info )
  189. {
  190. Detonate( );
  191. }
  192. #if !defined( CLIENT_DLL )
  193. //-----------------------------------------------------------------------------
  194. // Purpose:
  195. //-----------------------------------------------------------------------------
  196. void CBaseGrenade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  197. {
  198. // Support player pickup
  199. if ( useType == USE_TOGGLE )
  200. {
  201. CBasePlayer *pPlayer = ToBasePlayer( pActivator );
  202. if ( pPlayer )
  203. {
  204. pPlayer->PickupObject( this );
  205. return;
  206. }
  207. }
  208. // Pass up so we still call any custom Use function
  209. BaseClass::Use( pActivator, pCaller, useType, value );
  210. }
  211. #endif
  212. //-----------------------------------------------------------------------------
  213. // Purpose: Timed grenade, this think is called when time runs out.
  214. //-----------------------------------------------------------------------------
  215. void CBaseGrenade::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  216. {
  217. SetThink( &CBaseGrenade::Detonate );
  218. SetNextThink( gpGlobals->curtime );
  219. }
  220. void CBaseGrenade::PreDetonate( void )
  221. {
  222. #if !defined( CLIENT_DLL )
  223. CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), 400, 1.5, this );
  224. #endif
  225. SetThink( &CBaseGrenade::Detonate );
  226. SetNextThink( gpGlobals->curtime + 1.5 );
  227. }
  228. void CBaseGrenade::Detonate( void )
  229. {
  230. trace_t tr;
  231. Vector vecSpot;// trace starts here!
  232. SetThink( NULL );
  233. vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );
  234. UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -32 ), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, & tr);
  235. if( tr.startsolid )
  236. {
  237. // Since we blindly moved the explosion origin vertically, we may have inadvertently moved the explosion into a solid,
  238. // in which case nothing is going to be harmed by the grenade's explosion because all subsequent traces will startsolid.
  239. // If this is the case, we do the downward trace again from the actual origin of the grenade. (sjb) 3/8/2007 (for ep2_outland_09)
  240. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -32), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr );
  241. }
  242. Explode( &tr, DMG_BLAST );
  243. if ( GetShakeAmplitude() )
  244. {
  245. UTIL_ScreenShake( GetAbsOrigin(), GetShakeAmplitude(), 150.0, 1.0, GetShakeRadius(), SHAKE_START );
  246. }
  247. }
  248. //
  249. // Contact grenade, explode when it touches something
  250. //
  251. void CBaseGrenade::ExplodeTouch( CBaseEntity *pOther )
  252. {
  253. trace_t tr;
  254. Vector vecSpot;// trace starts here!
  255. Assert( pOther );
  256. if ( !pOther->IsSolid() )
  257. return;
  258. Vector velDir = GetAbsVelocity();
  259. VectorNormalize( velDir );
  260. vecSpot = GetAbsOrigin() - velDir * 32;
  261. UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  262. Explode( &tr, DMG_BLAST );
  263. }
  264. void CBaseGrenade::DangerSoundThink( void )
  265. {
  266. if (!IsInWorld())
  267. {
  268. Remove( );
  269. return;
  270. }
  271. #if !defined( CLIENT_DLL )
  272. CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, GetAbsVelocity().Length( ), 0.2, this );
  273. #endif
  274. SetNextThink( gpGlobals->curtime + 0.2 );
  275. if (GetWaterLevel() != 0)
  276. {
  277. SetAbsVelocity( GetAbsVelocity() * 0.5 );
  278. }
  279. }
  280. void CBaseGrenade::BounceTouch( CBaseEntity *pOther )
  281. {
  282. if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
  283. return;
  284. // don't hit the guy that launched this grenade
  285. if ( pOther == GetThrower() )
  286. return;
  287. // only do damage if we're moving fairly fast
  288. if ( (pOther->m_takedamage != DAMAGE_NO) && (m_flNextAttack < gpGlobals->curtime && GetAbsVelocity().Length() > 100))
  289. {
  290. if (m_hThrower)
  291. {
  292. #if !defined( CLIENT_DLL )
  293. trace_t tr;
  294. tr = CBaseEntity::GetTouchTrace( );
  295. ClearMultiDamage( );
  296. Vector forward;
  297. AngleVectors( GetLocalAngles(), &forward, NULL, NULL );
  298. CTakeDamageInfo info( this, m_hThrower, 1, DMG_CLUB );
  299. CalculateMeleeDamageForce( &info, GetAbsVelocity(), GetAbsOrigin() );
  300. pOther->DispatchTraceAttack( info, forward, &tr );
  301. ApplyMultiDamage();
  302. #endif
  303. }
  304. m_flNextAttack = gpGlobals->curtime + 1.0; // debounce
  305. }
  306. Vector vecTestVelocity;
  307. // m_vecAngVelocity = Vector (300, 300, 300);
  308. // this is my heuristic for modulating the grenade velocity because grenades dropped purely vertical
  309. // or thrown very far tend to slow down too quickly for me to always catch just by testing velocity.
  310. // trimming the Z velocity a bit seems to help quite a bit.
  311. vecTestVelocity = GetAbsVelocity();
  312. vecTestVelocity.z *= 0.45;
  313. if ( !m_bHasWarnedAI && vecTestVelocity.Length() <= 60 )
  314. {
  315. // grenade is moving really slow. It's probably very close to where it will ultimately stop moving.
  316. // emit the danger sound.
  317. // register a radius louder than the explosion, so we make sure everyone gets out of the way
  318. #if !defined( CLIENT_DLL )
  319. CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), m_flDamage / 0.4, 0.3, this );
  320. #endif
  321. m_bHasWarnedAI = true;
  322. }
  323. if (GetFlags() & FL_ONGROUND)
  324. {
  325. // add a bit of static friction
  326. // SetAbsVelocity( GetAbsVelocity() * 0.8 );
  327. // SetSequence( random->RandomInt( 1, 1 ) ); // FIXME: missing tumble animations
  328. }
  329. else
  330. {
  331. // play bounce sound
  332. BounceSound();
  333. }
  334. m_flPlaybackRate = GetAbsVelocity().Length() / 200.0;
  335. if (m_flPlaybackRate > 1.0)
  336. m_flPlaybackRate = 1;
  337. else if (m_flPlaybackRate < 0.5)
  338. m_flPlaybackRate = 0;
  339. }
  340. void CBaseGrenade::SlideTouch( CBaseEntity *pOther )
  341. {
  342. // don't hit the guy that launched this grenade
  343. if ( pOther == GetThrower() )
  344. return;
  345. // m_vecAngVelocity = Vector (300, 300, 300);
  346. if (GetFlags() & FL_ONGROUND)
  347. {
  348. // add a bit of static friction
  349. // SetAbsVelocity( GetAbsVelocity() * 0.95 );
  350. if (GetAbsVelocity().x != 0 || GetAbsVelocity().y != 0)
  351. {
  352. // maintain sliding sound
  353. }
  354. }
  355. else
  356. {
  357. BounceSound();
  358. }
  359. }
  360. void CBaseGrenade ::BounceSound( void )
  361. {
  362. // Doesn't need to do anything anymore! Physics makes the sound.
  363. }
  364. void CBaseGrenade ::TumbleThink( void )
  365. {
  366. if (!IsInWorld())
  367. {
  368. Remove( );
  369. return;
  370. }
  371. StudioFrameAdvance( );
  372. SetNextThink( gpGlobals->curtime + 0.1f );
  373. //
  374. // Emit a danger sound one second before exploding.
  375. //
  376. if (m_flDetonateTime - 1 < gpGlobals->curtime)
  377. {
  378. #if !defined( CLIENT_DLL )
  379. CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * (m_flDetonateTime - gpGlobals->curtime), 400, 0.1, this );
  380. #endif
  381. }
  382. if (m_flDetonateTime <= gpGlobals->curtime)
  383. {
  384. SetThink( &CBaseGrenade::Detonate );
  385. }
  386. if (GetWaterLevel() != 0)
  387. {
  388. SetAbsVelocity( GetAbsVelocity() * 0.5 );
  389. m_flPlaybackRate = 0.2;
  390. }
  391. }
  392. void CBaseGrenade::Precache( void )
  393. {
  394. BaseClass::Precache( );
  395. PrecacheScriptSound( "BaseGrenade.Explode" );
  396. }
  397. //-----------------------------------------------------------------------------
  398. // Purpose:
  399. // Output : CBaseCombatCharacter
  400. //-----------------------------------------------------------------------------
  401. CBaseCombatCharacter *CBaseGrenade::GetThrower( void )
  402. {
  403. CBaseCombatCharacter *pResult = ToBaseCombatCharacter( m_hThrower );
  404. if ( !pResult && GetOwnerEntity() != NULL )
  405. {
  406. pResult = ToBaseCombatCharacter( GetOwnerEntity() );
  407. }
  408. return pResult;
  409. }
  410. //-----------------------------------------------------------------------------
  411. void CBaseGrenade::SetThrower( CBaseCombatCharacter *pThrower )
  412. {
  413. m_hThrower = pThrower;
  414. // if this is the first thrower, set it as the original thrower
  415. if ( NULL == m_hOriginalThrower )
  416. {
  417. m_hOriginalThrower = pThrower;
  418. }
  419. }
  420. //-----------------------------------------------------------------------------
  421. // Purpose: Destructor
  422. // Input :
  423. // Output :
  424. //-----------------------------------------------------------------------------
  425. CBaseGrenade::~CBaseGrenade(void)
  426. {
  427. };
  428. //-----------------------------------------------------------------------------
  429. // Purpose: Constructor
  430. // Input :
  431. // Output :
  432. //-----------------------------------------------------------------------------
  433. CBaseGrenade::CBaseGrenade(void)
  434. {
  435. m_hThrower = NULL;
  436. m_hOriginalThrower = NULL;
  437. m_bIsLive = false;
  438. m_DmgRadius = 100;
  439. m_flDetonateTime = 0;
  440. m_bHasWarnedAI = false;
  441. SetSimulatedEveryTick( true );
  442. };