Counter Strike : Global Offensive Source Code
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.

365 lines
11 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "smokegrenade_projectile.h"
  8. #include "weapon_csbase.h"
  9. #include "particle_parse.h"
  10. #if defined( CLIENT_DLL )
  11. #include "c_cs_player.h"
  12. #else
  13. #include "sendproxy.h"
  14. #include "particle_smokegrenade.h"
  15. #include "cs_player.h"
  16. #include "keyvalues.h"
  17. #include "bot_manager.h"
  18. #include "Effects/inferno.h"
  19. #endif
  20. #define GRENADE_MODEL "models/Weapons/w_eq_smokegrenade_thrown.mdl"
  21. #if defined( CLIENT_DLL )
  22. IMPLEMENT_CLIENTCLASS_DT( C_SmokeGrenadeProjectile, DT_SmokeGrenadeProjectile, CSmokeGrenadeProjectile )
  23. RecvPropBool( RECVINFO( m_bDidSmokeEffect ) ),
  24. RecvPropInt( RECVINFO( m_nSmokeEffectTickBegin ) )
  25. END_RECV_TABLE()
  26. C_SmokeGrenadeProjectile::~C_SmokeGrenadeProjectile()
  27. {
  28. RemoveSmokeGrenadeHandle( this );
  29. }
  30. void C_SmokeGrenadeProjectile::PostDataUpdate( DataUpdateType_t type )
  31. {
  32. BaseClass::PostDataUpdate( type );
  33. }
  34. void C_SmokeGrenadeProjectile::OnDataChanged( DataUpdateType_t updateType )
  35. {
  36. if ( ( m_nSmokeEffectTickBegin || m_bDidSmokeEffect ) && !m_bSmokeEffectSpawned )
  37. {
  38. SpawnSmokeEffect();
  39. // And the smoke grenade particle began! - every call but the first is extraneous here
  40. AddSmokeGrenadeHandle( this );
  41. }
  42. }
  43. void C_SmokeGrenadeProjectile::SpawnSmokeEffect( )
  44. {
  45. if ( !m_bSmokeEffectSpawned )
  46. {
  47. m_bSmokeEffectSpawned = true;
  48. CNewParticleEffect *pSmokeEffect = NULL;
  49. // Used to be:
  50. int nUseMethod = 2;
  51. if ( nUseMethod == 0 )
  52. {
  53. // this is the closest to the old method; it doesn't let us correct the lifetime of the particle system in case of full frame update
  54. DispatchParticleEffect( "explosion_smokegrenade", GetAbsOrigin(), QAngle( 0, 0, 0 ) );// note QAngle(0,0,0). But we need to simulate the particle effect forward sometimes, so we need to use different API now.
  55. }
  56. else
  57. {
  58. Vector vOrigin = GetNetworkOrigin();
  59. if ( nUseMethod == 1 )
  60. {
  61. // This method works, but isn't the closest to the old method. The old method used CNewParticleEffect::CreateOrAggregate() API in its guts, but aggregation is implicitly disabled by explosion_smokegrenade particle definition as of Dec 2015 in CSGO staging.
  62. pSmokeEffect = ParticleProp()->Create( "explosion_smokegrenade", PATTACH_CUSTOMORIGIN );
  63. }
  64. else
  65. {
  66. // The old method used CNewParticleEffect::CreateOrAggregate() API in its guts, so this is the closest method to create smoke to the old method, but it's not been tested in trunk
  67. pSmokeEffect = CNewParticleEffect::CreateOrAggregate( NULL, "explosion_smokegrenade", vOrigin );
  68. }
  69. if ( pSmokeEffect )
  70. {
  71. pSmokeEffect->SetSortOrigin( vOrigin );
  72. pSmokeEffect->SetControlPoint( 0, vOrigin );
  73. pSmokeEffect->SetControlPoint( 1, vOrigin );
  74. pSmokeEffect->SetControlPointOrientation( 0, Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), Vector( 0, 0, 1 ) );
  75. }
  76. }
  77. if ( m_nSmokeEffectTickBegin )
  78. {
  79. int nSkipFrames = gpGlobals->tickcount - m_nSmokeEffectTickBegin;
  80. if ( nSkipFrames > 4 && pSmokeEffect )
  81. {
  82. //Note: pSmokeEffect->Simulate( flSkipSeconds ); would be ideal, but it doesn't work well for long intervals. SkipToTime would be even better but it will extinguish the particle effect if it skips past 2 seconds due to some perf heuristic, and it's not clear if it skips correctly either.
  83. // this doesn't happen often, and when it does, it's on connection or on replay begin/end, so a little hitch shouldn't be a problem.
  84. for ( int i = 2; i < nSkipFrames; i += 2 )
  85. pSmokeEffect->Simulate( gpGlobals->interval_per_tick * 2 );
  86. }
  87. }
  88. }
  89. }
  90. #else // GAME_DLL
  91. LINK_ENTITY_TO_CLASS( smokegrenade_projectile, CSmokeGrenadeProjectile );
  92. PRECACHE_REGISTER( smokegrenade_projectile );
  93. IMPLEMENT_SERVERCLASS_ST( CSmokeGrenadeProjectile, DT_SmokeGrenadeProjectile )
  94. SendPropBool( SENDINFO( m_bDidSmokeEffect ) ),
  95. SendPropInt( SENDINFO( m_nSmokeEffectTickBegin ) )
  96. END_SEND_TABLE()
  97. BEGIN_DATADESC( CSmokeGrenadeProjectile )
  98. DEFINE_THINKFUNC( Think_Detonate ),
  99. DEFINE_THINKFUNC( Think_Fade ),
  100. DEFINE_THINKFUNC( Think_Remove )
  101. END_DATADESC()
  102. CSmokeGrenadeProjectile* CSmokeGrenadeProjectile::Create(
  103. const Vector &position,
  104. const QAngle &angles,
  105. const Vector &velocity,
  106. const AngularImpulse &angVelocity,
  107. CBaseCombatCharacter *pOwner,
  108. const CCSWeaponInfo& weaponInfo )
  109. {
  110. CSmokeGrenadeProjectile *pGrenade = (CSmokeGrenadeProjectile*)CBaseEntity::Create( "smokegrenade_projectile", position, angles, pOwner );
  111. // Set the timer for 1 second less than requested. We're going to issue a SOUND_DANGER
  112. // one second before detonation.
  113. pGrenade->SetTimer( 1.5 );
  114. pGrenade->SetAbsVelocity( velocity );
  115. pGrenade->SetupInitialTransmittedGrenadeVelocity( velocity );
  116. pGrenade->SetThrower( pOwner );
  117. pGrenade->SetGravity( 0.55 );
  118. pGrenade->SetFriction( 0.7 );
  119. pGrenade->m_flDamage = 100;
  120. pGrenade->ChangeTeam( pOwner->GetTeamNumber() );
  121. pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );
  122. pGrenade->SetTouch( &CBaseGrenade::BounceTouch );
  123. pGrenade->SetGravity( BaseClass::GetGrenadeGravity() );
  124. pGrenade->SetFriction( BaseClass::GetGrenadeFriction() );
  125. pGrenade->SetElasticity( BaseClass::GetGrenadeElasticity() );
  126. pGrenade->m_bDidSmokeEffect = false;
  127. pGrenade->m_nSmokeEffectTickBegin = 0;
  128. pGrenade->m_flLastBounce = 0;
  129. pGrenade->m_vSmokeColor = weaponInfo.GetSmokeColor();
  130. pGrenade->m_pWeaponInfo = &weaponInfo;
  131. pGrenade->SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
  132. return pGrenade;
  133. }
  134. void CSmokeGrenadeProjectile::SetTimer( float timer )
  135. {
  136. SetThink( &CSmokeGrenadeProjectile::Think_Detonate );
  137. SetNextThink( gpGlobals->curtime + timer );
  138. TheBots->SetGrenadeRadius( this, 0.0f );
  139. }
  140. void CSmokeGrenadeProjectile::Think_Detonate()
  141. {
  142. if ( GetAbsVelocity().Length() > 0.1 )
  143. {
  144. // Still moving. Don't detonate yet.
  145. SetNextThink( gpGlobals->curtime + 0.2 );
  146. return;
  147. }
  148. SmokeDetonate();
  149. }
  150. void CSmokeGrenadeProjectile::SmokeDetonate( void )
  151. {
  152. TheBots->SetGrenadeRadius( this, SmokeGrenadeRadius );
  153. // Ok, we've stopped rolling or whatever. Now detonate.
  154. // Make sure all players get the message about this smoke effect.
  155. // This fixes an exploit where a player could enter a room where others were seeing smoke and he wasn't
  156. // because he wasn't in the PVS when the smoke effect started.
  157. m_nSmokeEffectTickBegin = gpGlobals->tickcount; // client will star the explosion_smokegrenade particle effect at AbsOrigin
  158. //tell the hostages about the smoke!
  159. CBaseEntity *pEntity = NULL;
  160. variant_t var; //send the location of the smoke?
  161. var.SetVector3D( GetAbsOrigin() );
  162. while ( ( pEntity = gEntList.FindEntityByClassname( pEntity, "hostage_entity" ) ) != NULL)
  163. {
  164. //send to hostages that have a resonable chance of being in it while its still smoking
  165. if( (GetAbsOrigin() - pEntity->GetAbsOrigin()).Length() < 1000 )
  166. pEntity->AcceptInput( "smokegrenade", this, this, var, 0 );
  167. }
  168. // tell the bots a smoke grenade has exploded
  169. CCSPlayer *player = ToCSPlayer(GetThrower());
  170. if ( player )
  171. {
  172. IGameEvent * event = gameeventmanager->CreateEvent( "smokegrenade_detonate" );
  173. if ( event )
  174. {
  175. event->SetInt( "userid", player->GetUserID() );
  176. event->SetInt( "entityid", this->entindex() );
  177. event->SetFloat( "x", GetAbsOrigin().x );
  178. event->SetFloat( "y", GetAbsOrigin().y );
  179. event->SetFloat( "z", GetAbsOrigin().z );
  180. gameeventmanager->FireEvent( event );
  181. }
  182. }
  183. // We avoid our base class detonation, so add the ogs record of our explsion here.
  184. // Note: this has to be after the gameevent is fired and serviced by server-side listeners
  185. // so we can run the 'smoke grenade extinguishing infernos' logic and have ogs relevant state set from that.
  186. RecordDetonation();
  187. m_bDidSmokeEffect = true; //<- the old way to signal the start of smoke effect; the new way is to set the particle start tick, so that we can replay and fix the bug when we lose the smoke effect when we connect right after smoke grenade went off
  188. EmitSound( "BaseSmokeEffect.Sound" );
  189. m_nRenderMode = kRenderTransColor;
  190. SetMoveType(MOVETYPE_NONE);
  191. SetNextThink( gpGlobals->curtime + 12.5f );
  192. SetThink( &CSmokeGrenadeProjectile::Think_Fade );
  193. SetSolid( SOLID_NONE );
  194. }
  195. void CSmokeGrenadeProjectile::RemoveGrenadeFromLists( void )
  196. {
  197. TheBots->RemoveGrenade( this );
  198. SetModelName( NULL_STRING );//invisible
  199. SetSolid( SOLID_NONE );
  200. CCSPlayer *player = ToCSPlayer(GetThrower());
  201. if ( player )
  202. {
  203. IGameEvent * event = gameeventmanager->CreateEvent( "smokegrenade_expired" );
  204. if ( event )
  205. {
  206. event->SetInt( "userid", player->GetUserID() );
  207. event->SetInt( "entityid", this->entindex() );
  208. event->SetFloat( "x", GetAbsOrigin().x );
  209. event->SetFloat( "y", GetAbsOrigin().y );
  210. event->SetFloat( "z", GetAbsOrigin().z );
  211. gameeventmanager->FireEvent( event );
  212. }
  213. }
  214. }
  215. // Fade the projectile out over time before making it disappear
  216. void CSmokeGrenadeProjectile::Think_Fade()
  217. {
  218. SetNextThink( gpGlobals->curtime );
  219. byte a = GetRenderAlpha();
  220. a -= 1;
  221. SetRenderAlpha( a );
  222. if ( !a )
  223. {
  224. //RemoveGrenadeFromLists();
  225. SetNextThink( gpGlobals->curtime + 1.0 );
  226. SetThink( &CSmokeGrenadeProjectile::Think_Remove ); // Spit out smoke for 10 seconds.
  227. }
  228. }
  229. void CSmokeGrenadeProjectile::Think_Remove()
  230. {
  231. RemoveGrenadeFromLists();
  232. SetMoveType( MOVETYPE_NONE );
  233. UTIL_Remove( this );
  234. }
  235. //Implement this so we never call the base class,
  236. //but this should never be called either.
  237. void CSmokeGrenadeProjectile::Detonate( void )
  238. {
  239. Assert(!"Smoke grenade handles its own detonation");
  240. }
  241. void CSmokeGrenadeProjectile::Spawn()
  242. {
  243. SetModel( GRENADE_MODEL );
  244. BaseClass::Spawn();
  245. SetBodygroupPreset( "thrown" );
  246. }
  247. void CSmokeGrenadeProjectile::Precache()
  248. {
  249. PrecacheModel( GRENADE_MODEL );
  250. PrecacheScriptSound( "BaseSmokeEffect.Sound" );
  251. PrecacheScriptSound( "SmokeGrenade.Bounce" );
  252. BaseClass::Precache();
  253. }
  254. void CSmokeGrenadeProjectile::OnBounced( void )
  255. {
  256. if ( m_flLastBounce >= ( gpGlobals->curtime - 3*gpGlobals->interval_per_tick ) )
  257. return;
  258. m_flLastBounce = gpGlobals->curtime;
  259. //
  260. // if the smoke grenade is above ground, trace down to the ground and see where it would end up?
  261. //
  262. Vector posDropSmoke = GetAbsOrigin();
  263. trace_t trSmokeTrace;
  264. UTIL_TraceLine( posDropSmoke, posDropSmoke - Vector( 0, 0, SmokeGrenadeRadius ), ( MASK_PLAYERSOLID & ~CONTENTS_PLAYERCLIP ),
  265. this, COLLISION_GROUP_PROJECTILE, &trSmokeTrace );
  266. if ( !trSmokeTrace.startsolid )
  267. {
  268. if ( trSmokeTrace.fraction >= 1.0f )
  269. return; // this smoke cannot drop enough to cause extinguish
  270. if ( trSmokeTrace.fraction > 0.001f )
  271. posDropSmoke = trSmokeTrace.endpos;
  272. }
  273. //
  274. // See if it touches any inferno?
  275. //
  276. const int maxEnts = 64;
  277. CBaseEntity *list[ maxEnts ];
  278. int count = UTIL_EntitiesInSphere( list, maxEnts, GetAbsOrigin(), 512, FL_ONFIRE );
  279. for( int i=0; i<count; ++i )
  280. {
  281. if (list[i] == NULL || list[i] == this)
  282. continue;
  283. CInferno* pInferno = dynamic_cast<CInferno*>( list[i] );
  284. if ( pInferno && pInferno->BShouldExtinguishSmokeGrenadeBounce( this, posDropSmoke ) )
  285. {
  286. if ( posDropSmoke != GetAbsOrigin() )
  287. {
  288. const QAngle qAngOriginZero = vec3_angle;
  289. const Vector vVelocityZero = vec3_origin;
  290. Teleport( &posDropSmoke, &qAngOriginZero, &vVelocityZero );
  291. }
  292. SmokeDetonate();
  293. break;
  294. }
  295. }
  296. }
  297. void CSmokeGrenadeProjectile::BounceSound( void )
  298. {
  299. if ( !m_bDidSmokeEffect )
  300. {
  301. EmitSound( "SmokeGrenade.Bounce" );
  302. }
  303. }
  304. #endif