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.

452 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "basegrenade_shared.h"
  9. #include "grenade_frag.h"
  10. #include "Sprite.h"
  11. #include "SpriteTrail.h"
  12. #include "soundent.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. #define FRAG_GRENADE_BLIP_FREQUENCY 1.0f
  16. #define FRAG_GRENADE_BLIP_FAST_FREQUENCY 0.3f
  17. #define FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP 1.5f
  18. #define FRAG_GRENADE_WARN_TIME 1.5f
  19. const float GRENADE_COEFFICIENT_OF_RESTITUTION = 0.2f;
  20. ConVar sk_plr_dmg_fraggrenade ( "sk_plr_dmg_fraggrenade","0");
  21. ConVar sk_npc_dmg_fraggrenade ( "sk_npc_dmg_fraggrenade","0");
  22. ConVar sk_fraggrenade_radius ( "sk_fraggrenade_radius", "0");
  23. #define GRENADE_MODEL "models/Weapons/w_grenade.mdl"
  24. class CGrenadeFrag : public CBaseGrenade
  25. {
  26. DECLARE_CLASS( CGrenadeFrag, CBaseGrenade );
  27. #if !defined( CLIENT_DLL )
  28. DECLARE_DATADESC();
  29. #endif
  30. ~CGrenadeFrag( void );
  31. public:
  32. void Spawn( void );
  33. void OnRestore( void );
  34. void Precache( void );
  35. bool CreateVPhysics( void );
  36. void CreateEffects( void );
  37. void SetTimer( float detonateDelay, float warnDelay );
  38. void SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity );
  39. int OnTakeDamage( const CTakeDamageInfo &inputInfo );
  40. void BlipSound() { EmitSound( "Grenade.Blip" ); }
  41. void DelayThink();
  42. void VPhysicsUpdate( IPhysicsObject *pPhysics );
  43. void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
  44. void SetCombineSpawned( bool combineSpawned ) { m_combineSpawned = combineSpawned; }
  45. bool IsCombineSpawned( void ) const { return m_combineSpawned; }
  46. void SetPunted( bool punt ) { m_punted = punt; }
  47. bool WasPunted( void ) const { return m_punted; }
  48. // this function only used in episodic.
  49. #if defined(HL2_EPISODIC) && 0 // FIXME: HandleInteraction() is no longer called now that base grenade derives from CBaseAnimating
  50. bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt);
  51. #endif
  52. void InputSetTimer( inputdata_t &inputdata );
  53. protected:
  54. CHandle<CSprite> m_pMainGlow;
  55. CHandle<CSpriteTrail> m_pGlowTrail;
  56. float m_flNextBlipTime;
  57. bool m_inSolid;
  58. bool m_combineSpawned;
  59. bool m_punted;
  60. };
  61. LINK_ENTITY_TO_CLASS( npc_grenade_frag, CGrenadeFrag );
  62. BEGIN_DATADESC( CGrenadeFrag )
  63. // Fields
  64. DEFINE_FIELD( m_pMainGlow, FIELD_EHANDLE ),
  65. DEFINE_FIELD( m_pGlowTrail, FIELD_EHANDLE ),
  66. DEFINE_FIELD( m_flNextBlipTime, FIELD_TIME ),
  67. DEFINE_FIELD( m_inSolid, FIELD_BOOLEAN ),
  68. DEFINE_FIELD( m_combineSpawned, FIELD_BOOLEAN ),
  69. DEFINE_FIELD( m_punted, FIELD_BOOLEAN ),
  70. // Function Pointers
  71. DEFINE_THINKFUNC( DelayThink ),
  72. // Inputs
  73. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTimer", InputSetTimer ),
  74. END_DATADESC()
  75. //-----------------------------------------------------------------------------
  76. // Purpose:
  77. //-----------------------------------------------------------------------------
  78. CGrenadeFrag::~CGrenadeFrag( void )
  79. {
  80. }
  81. void CGrenadeFrag::Spawn( void )
  82. {
  83. Precache( );
  84. SetModel( GRENADE_MODEL );
  85. if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() )
  86. {
  87. m_flDamage = sk_plr_dmg_fraggrenade.GetFloat();
  88. m_DmgRadius = sk_fraggrenade_radius.GetFloat();
  89. }
  90. else
  91. {
  92. m_flDamage = sk_npc_dmg_fraggrenade.GetFloat();
  93. m_DmgRadius = sk_fraggrenade_radius.GetFloat();
  94. }
  95. m_takedamage = DAMAGE_YES;
  96. m_iHealth = 1;
  97. SetSize( -Vector(4,4,4), Vector(4,4,4) );
  98. SetCollisionGroup( COLLISION_GROUP_WEAPON );
  99. CreateVPhysics();
  100. BlipSound();
  101. m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FREQUENCY;
  102. AddSolidFlags( FSOLID_NOT_STANDABLE );
  103. m_combineSpawned = false;
  104. m_punted = false;
  105. BaseClass::Spawn();
  106. }
  107. //-----------------------------------------------------------------------------
  108. // Purpose:
  109. //-----------------------------------------------------------------------------
  110. void CGrenadeFrag::OnRestore( void )
  111. {
  112. // If we were primed and ready to detonate, put FX on us.
  113. if (m_flDetonateTime > 0)
  114. CreateEffects();
  115. BaseClass::OnRestore();
  116. }
  117. //-----------------------------------------------------------------------------
  118. // Purpose:
  119. //-----------------------------------------------------------------------------
  120. void CGrenadeFrag::CreateEffects( void )
  121. {
  122. // Start up the eye glow
  123. m_pMainGlow = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin(), false );
  124. int nAttachment = LookupAttachment( "fuse" );
  125. if ( m_pMainGlow != NULL )
  126. {
  127. m_pMainGlow->FollowEntity( this );
  128. m_pMainGlow->SetAttachment( this, nAttachment );
  129. m_pMainGlow->SetTransparency( kRenderGlow, 255, 255, 255, 200, kRenderFxNoDissipation );
  130. m_pMainGlow->SetScale( 0.2f );
  131. m_pMainGlow->SetGlowProxySize( 4.0f );
  132. }
  133. // Start up the eye trail
  134. m_pGlowTrail = CSpriteTrail::SpriteTrailCreate( "sprites/bluelaser1.vmt", GetLocalOrigin(), false );
  135. if ( m_pGlowTrail != NULL )
  136. {
  137. m_pGlowTrail->FollowEntity( this );
  138. m_pGlowTrail->SetAttachment( this, nAttachment );
  139. m_pGlowTrail->SetTransparency( kRenderTransAdd, 255, 0, 0, 255, kRenderFxNone );
  140. m_pGlowTrail->SetStartWidth( 8.0f );
  141. m_pGlowTrail->SetEndWidth( 1.0f );
  142. m_pGlowTrail->SetLifeTime( 0.5f );
  143. }
  144. }
  145. bool CGrenadeFrag::CreateVPhysics()
  146. {
  147. // Create the object in the physics system
  148. VPhysicsInitNormal( SOLID_BBOX, 0, false );
  149. return true;
  150. }
  151. // this will hit only things that are in newCollisionGroup, but NOT in collisionGroupAlreadyChecked
  152. class CTraceFilterCollisionGroupDelta : public CTraceFilterEntitiesOnly
  153. {
  154. public:
  155. // It does have a base, but we'll never network anything below here..
  156. DECLARE_CLASS_NOBASE( CTraceFilterCollisionGroupDelta );
  157. CTraceFilterCollisionGroupDelta( const IHandleEntity *passentity, int collisionGroupAlreadyChecked, int newCollisionGroup )
  158. : m_pPassEnt(passentity), m_collisionGroupAlreadyChecked( collisionGroupAlreadyChecked ), m_newCollisionGroup( newCollisionGroup )
  159. {
  160. }
  161. virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  162. {
  163. if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
  164. return false;
  165. CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
  166. if ( pEntity )
  167. {
  168. if ( g_pGameRules->ShouldCollide( m_collisionGroupAlreadyChecked, pEntity->GetCollisionGroup() ) )
  169. return false;
  170. if ( g_pGameRules->ShouldCollide( m_newCollisionGroup, pEntity->GetCollisionGroup() ) )
  171. return true;
  172. }
  173. return false;
  174. }
  175. protected:
  176. const IHandleEntity *m_pPassEnt;
  177. int m_collisionGroupAlreadyChecked;
  178. int m_newCollisionGroup;
  179. };
  180. void CGrenadeFrag::VPhysicsUpdate( IPhysicsObject *pPhysics )
  181. {
  182. BaseClass::VPhysicsUpdate( pPhysics );
  183. Vector vel;
  184. AngularImpulse angVel;
  185. pPhysics->GetVelocity( &vel, &angVel );
  186. Vector start = GetAbsOrigin();
  187. // find all entities that my collision group wouldn't hit, but COLLISION_GROUP_NONE would and bounce off of them as a ray cast
  188. CTraceFilterCollisionGroupDelta filter( this, GetCollisionGroup(), COLLISION_GROUP_NONE );
  189. trace_t tr;
  190. // UNDONE: Hull won't work with hitboxes - hits outer hull. But the whole point of this test is to hit hitboxes.
  191. #if 0
  192. UTIL_TraceHull( start, start + vel * gpGlobals->frametime, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr );
  193. #else
  194. UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr );
  195. #endif
  196. if ( tr.startsolid )
  197. {
  198. if ( !m_inSolid )
  199. {
  200. // UNDONE: Do a better contact solution that uses relative velocity?
  201. vel *= -GRENADE_COEFFICIENT_OF_RESTITUTION; // bounce backwards
  202. pPhysics->SetVelocity( &vel, NULL );
  203. }
  204. m_inSolid = true;
  205. return;
  206. }
  207. m_inSolid = false;
  208. if ( tr.DidHit() )
  209. {
  210. Vector dir = vel;
  211. VectorNormalize(dir);
  212. // send a tiny amount of damage so the character will react to getting bonked
  213. CTakeDamageInfo info( this, GetThrower(), pPhysics->GetMass() * vel, GetAbsOrigin(), 0.1f, DMG_CRUSH );
  214. tr.m_pEnt->TakeDamage( info );
  215. // reflect velocity around normal
  216. vel = -2.0f * tr.plane.normal * DotProduct(vel,tr.plane.normal) + vel;
  217. // absorb 80% in impact
  218. vel *= GRENADE_COEFFICIENT_OF_RESTITUTION;
  219. angVel *= -0.5f;
  220. pPhysics->SetVelocity( &vel, &angVel );
  221. }
  222. }
  223. void CGrenadeFrag::Precache( void )
  224. {
  225. PrecacheModel( GRENADE_MODEL );
  226. PrecacheScriptSound( "Grenade.Blip" );
  227. PrecacheModel( "sprites/redglow1.vmt" );
  228. PrecacheModel( "sprites/bluelaser1.vmt" );
  229. BaseClass::Precache();
  230. }
  231. void CGrenadeFrag::SetTimer( float detonateDelay, float warnDelay )
  232. {
  233. m_flDetonateTime = gpGlobals->curtime + detonateDelay;
  234. m_flWarnAITime = gpGlobals->curtime + warnDelay;
  235. SetThink( &CGrenadeFrag::DelayThink );
  236. SetNextThink( gpGlobals->curtime );
  237. CreateEffects();
  238. }
  239. void CGrenadeFrag::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  240. {
  241. SetThrower( pPhysGunUser );
  242. #ifdef HL2MP
  243. SetTimer( FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP, FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP / 2);
  244. BlipSound();
  245. m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FAST_FREQUENCY;
  246. m_bHasWarnedAI = true;
  247. #else
  248. if( IsX360() )
  249. {
  250. // Give 'em a couple of seconds to aim and throw.
  251. SetTimer( 2.0f, 1.0f);
  252. BlipSound();
  253. m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FAST_FREQUENCY;
  254. }
  255. #endif
  256. #ifdef HL2_EPISODIC
  257. SetPunted( true );
  258. #endif
  259. BaseClass::OnPhysGunPickup( pPhysGunUser, reason );
  260. }
  261. void CGrenadeFrag::DelayThink()
  262. {
  263. if( gpGlobals->curtime > m_flDetonateTime )
  264. {
  265. Detonate();
  266. return;
  267. }
  268. if( !m_bHasWarnedAI && gpGlobals->curtime >= m_flWarnAITime )
  269. {
  270. #if !defined( CLIENT_DLL )
  271. CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), 400, 1.5, this );
  272. #endif
  273. m_bHasWarnedAI = true;
  274. }
  275. if( gpGlobals->curtime > m_flNextBlipTime )
  276. {
  277. BlipSound();
  278. if( m_bHasWarnedAI )
  279. {
  280. m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FAST_FREQUENCY;
  281. }
  282. else
  283. {
  284. m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FREQUENCY;
  285. }
  286. }
  287. SetNextThink( gpGlobals->curtime + 0.1 );
  288. }
  289. void CGrenadeFrag::SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity )
  290. {
  291. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  292. if ( pPhysicsObject )
  293. {
  294. pPhysicsObject->AddVelocity( &velocity, &angVelocity );
  295. }
  296. }
  297. int CGrenadeFrag::OnTakeDamage( const CTakeDamageInfo &inputInfo )
  298. {
  299. // Manually apply vphysics because BaseCombatCharacter takedamage doesn't call back to CBaseEntity OnTakeDamage
  300. VPhysicsTakeDamage( inputInfo );
  301. // Grenades only suffer blast damage and burn damage.
  302. if( !(inputInfo.GetDamageType() & (DMG_BLAST|DMG_BURN) ) )
  303. return 0;
  304. return BaseClass::OnTakeDamage( inputInfo );
  305. }
  306. #if defined(HL2_EPISODIC) && 0 // FIXME: HandleInteraction() is no longer called now that base grenade derives from CBaseAnimating
  307. extern int g_interactionBarnacleVictimGrab; ///< usually declared in ai_interactions.h but no reason to haul all of that in here.
  308. extern int g_interactionBarnacleVictimBite;
  309. extern int g_interactionBarnacleVictimReleased;
  310. bool CGrenadeFrag::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
  311. {
  312. // allow fragnades to be grabbed by barnacles.
  313. if ( interactionType == g_interactionBarnacleVictimGrab )
  314. {
  315. // give the grenade another five seconds seconds so the player can have the satisfaction of blowing up the barnacle with it
  316. float timer = m_flDetonateTime - gpGlobals->curtime + 5.0f;
  317. SetTimer( timer, timer - FRAG_GRENADE_WARN_TIME );
  318. return true;
  319. }
  320. else if ( interactionType == g_interactionBarnacleVictimBite )
  321. {
  322. // detonate the grenade immediately
  323. SetTimer( 0, 0 );
  324. return true;
  325. }
  326. else if ( interactionType == g_interactionBarnacleVictimReleased )
  327. {
  328. // take the five seconds back off the timer.
  329. float timer = MAX(m_flDetonateTime - gpGlobals->curtime - 5.0f,0.0f);
  330. SetTimer( timer, timer - FRAG_GRENADE_WARN_TIME );
  331. return true;
  332. }
  333. else
  334. {
  335. return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
  336. }
  337. }
  338. #endif
  339. void CGrenadeFrag::InputSetTimer( inputdata_t &inputdata )
  340. {
  341. SetTimer( inputdata.value.Float(), inputdata.value.Float() - FRAG_GRENADE_WARN_TIME );
  342. }
  343. CBaseGrenade *Fraggrenade_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer, bool combineSpawned )
  344. {
  345. // Don't set the owner here, or the player can't interact with grenades he's thrown
  346. CGrenadeFrag *pGrenade = (CGrenadeFrag *)CBaseEntity::Create( "npc_grenade_frag", position, angles, pOwner );
  347. pGrenade->SetTimer( timer, timer - FRAG_GRENADE_WARN_TIME );
  348. pGrenade->SetVelocity( velocity, angVelocity );
  349. pGrenade->SetThrower( ToBaseCombatCharacter( pOwner ) );
  350. pGrenade->m_takedamage = DAMAGE_EVENTS_ONLY;
  351. pGrenade->SetCombineSpawned( combineSpawned );
  352. return pGrenade;
  353. }
  354. bool Fraggrenade_WasPunted( const CBaseEntity *pEntity )
  355. {
  356. const CGrenadeFrag *pFrag = dynamic_cast<const CGrenadeFrag *>( pEntity );
  357. if ( pFrag )
  358. {
  359. return pFrag->WasPunted();
  360. }
  361. return false;
  362. }
  363. bool Fraggrenade_WasCreatedByCombine( const CBaseEntity *pEntity )
  364. {
  365. const CGrenadeFrag *pFrag = dynamic_cast<const CGrenadeFrag *>( pEntity );
  366. if ( pFrag )
  367. {
  368. return pFrag->IsCombineSpawned();
  369. }
  370. return false;
  371. }