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.

508 lines
16 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // TF Energy Ring
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "tf_projectile_energy_ring.h"
  8. #include "tf_weapon_raygun.h"
  9. #ifdef CLIENT_DLL
  10. #include "c_basetempentity.h"
  11. #include "c_te_legacytempents.h"
  12. #include "c_te_effect_dispatch.h"
  13. #include "input.h"
  14. #include "c_tf_player.h"
  15. #include "cliententitylist.h"
  16. #endif
  17. #ifdef GAME_DLL
  18. #include "tf_player.h"
  19. #include "tf_player_shared.h"
  20. #include "particle_parse.h"
  21. #include "tf_pumpkin_bomb.h"
  22. #include "halloween/merasmus/merasmus_trick_or_treat_prop.h"
  23. #include "tf_robot_destruction_robot.h"
  24. #endif
  25. #define ENERGY_RING_DISPATCH_EFFECT "ClientProjectile_EnergyRing"
  26. #define ENERGY_RING_DISPATCH_EFFECT_POMSON "ClientProjectile_EnergyRingPomson"
  27. const char* g_pszEnergyRingModel ( "models/weapons/w_models/w_drg_ball.mdl" );
  28. const char* g_pszPomsonImpactFleshSound ( "Weapon_Pomson.ProjectileImpactWorld" );
  29. const char* g_pszPomsonImpactWorldSound ( "Weapon_Pomson.ProjectileImpactFlesh" );
  30. const char* g_pszPomsonTrailParticle ( "drg_pomson_projectile" );
  31. const char* g_pszPomsonTrailParticleCrit ( "drg_pomson_projectile_crit" );
  32. const char* g_pszBisonImpactFleshSound ( "Weapon_Bison.ProjectileImpactWorld" );
  33. const char* g_pszBisonImpactWorldSound ( "Weapon_Bison.ProjectileImpactFlesh" );
  34. const char* g_pszBisonTrailParticle ( "drg_bison_projectile" );
  35. const char* g_pszBisonTrailParticleCrit ( "drg_bison_projectile_crit" );
  36. const char* g_pszEnergyProjectileImpactParticle ( "drg_pomson_impact" );
  37. //=============================================================================
  38. //
  39. // TF Energy Ring Projectile functions
  40. //
  41. IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_EnergyRing, DT_TFProjectile_EnergyRing )
  42. BEGIN_NETWORK_TABLE( CTFProjectile_EnergyRing, DT_TFProjectile_EnergyRing )
  43. END_NETWORK_TABLE()
  44. //-----------------------------------------------------------------------------
  45. LINK_ENTITY_TO_CLASS( tf_projectile_energy_ring, CTFProjectile_EnergyRing );
  46. PRECACHE_WEAPON_REGISTER( tf_projectile_energy_ring );
  47. short g_sModelIndexRing;
  48. void PrecacheRing(void *pUser)
  49. {
  50. g_sModelIndexRing = modelinfo->GetModelIndex( g_pszEnergyRingModel );
  51. }
  52. PRECACHE_REGISTER_FN(PrecacheRing);
  53. //-----------------------------------------------------------------------------
  54. // Purpose:
  55. //-----------------------------------------------------------------------------
  56. CTFProjectile_EnergyRing::CTFProjectile_EnergyRing()
  57. {
  58. m_vecPrevPos = vec3_origin;
  59. }
  60. //-----------------------------------------------------------------------------
  61. // Purpose:
  62. //-----------------------------------------------------------------------------
  63. const char *CTFProjectile_EnergyRing::GetProjectileModelName( void )
  64. {
  65. return g_pszEnergyRingModel;
  66. }
  67. //-----------------------------------------------------------------------------
  68. // Purpose:
  69. //-----------------------------------------------------------------------------
  70. float CTFProjectile_EnergyRing::GetGravity( void )
  71. {
  72. return 0.f;
  73. }
  74. float CTFProjectile_EnergyRing::GetInitialVelocity( void )
  75. {
  76. return ShouldPenetrate() ? 840.f : 1200.f;
  77. }
  78. //-----------------------------------------------------------------------------
  79. // Purpose:
  80. //-----------------------------------------------------------------------------
  81. CTFProjectile_EnergyRing *CTFProjectile_EnergyRing::Create( CTFWeaponBaseGun *pLauncher, const Vector &vecOrigin, const QAngle& vecAngles, float fSpeed, float fGravity,
  82. CBaseEntity *pOwner, CBaseEntity *pScorer, Vector vColor1, Vector vColor2, bool bCritical )
  83. {
  84. CTFProjectile_EnergyRing *pRing = NULL;
  85. #ifdef GAME_DLL
  86. Vector vecForward, vecRight, vecUp;
  87. AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp );
  88. pRing = static_cast<CTFProjectile_EnergyRing*>( CBaseEntity::Create( "tf_projectile_energy_ring", vecOrigin, vecAngles, pOwner ) );
  89. if ( !pRing )
  90. return NULL;
  91. // Initialize the owner.
  92. pRing->SetOwnerEntity( pOwner );
  93. pRing->SetLauncher( pLauncher );
  94. pRing->SetScorer( pScorer );
  95. // Spawn.
  96. pRing->Spawn();
  97. Vector vecVelocity = vecForward * pRing->GetInitialVelocity();
  98. pRing->SetAbsVelocity( vecVelocity );
  99. // Setup the initial angles.
  100. QAngle angles;
  101. VectorAngles( vecVelocity, angles );
  102. pRing->SetAbsAngles( angles );
  103. // Set team.
  104. pRing->ChangeTeam( pOwner->GetTeamNumber() );
  105. if ( pScorer )
  106. {
  107. pRing->SetTruceValidForEnt( pScorer->IsTruceValidForEnt() );
  108. }
  109. #endif
  110. #ifdef CLIENT_DLL
  111. // This is silly code to support demos when the client created its own effects
  112. // for the Pomson and Righteous Bison
  113. CTFRaygun* pRaygun = assert_cast< CTFRaygun* >( pLauncher );
  114. if ( pRaygun && !pRaygun->UseNewProjectileCode() )
  115. {
  116. if ( pRaygun->GetWeaponID() == TF_WEAPON_DRG_POMSON )
  117. {
  118. pRing = static_cast<CTFProjectile_EnergyRing*>( CTFBaseProjectile::Create( "tf_projectile_energy_ring", vecOrigin, vecAngles, pOwner,
  119. 1200.f, g_sModelIndexRing,
  120. ENERGY_RING_DISPATCH_EFFECT_POMSON, pScorer, bCritical, vColor1, vColor2 ) );
  121. }
  122. else
  123. {
  124. pRing = static_cast<CTFProjectile_EnergyRing*>( CTFBaseProjectile::Create( "tf_projectile_energy_ring", vecOrigin, vecAngles, pOwner,
  125. 1200.f, g_sModelIndexRing,
  126. ENERGY_RING_DISPATCH_EFFECT, pScorer, bCritical, vColor1, vColor2 ) );
  127. }
  128. if ( pRing )
  129. {
  130. pRing->SetRenderMode( kRenderNone );
  131. pRing->SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
  132. pRing->SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS );
  133. }
  134. }
  135. #endif
  136. return pRing;
  137. }
  138. //-----------------------------------------------------------------------------
  139. // Purpose:
  140. //-----------------------------------------------------------------------------
  141. void CTFProjectile_EnergyRing::Spawn()
  142. {
  143. BaseClass::Spawn();
  144. SetSolid( SOLID_BBOX );
  145. SetMoveType( MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM );
  146. SetRenderMode( kRenderNone );
  147. SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
  148. SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS );
  149. }
  150. //-----------------------------------------------------------------------------
  151. // Purpose:
  152. //-----------------------------------------------------------------------------
  153. void CTFProjectile_EnergyRing::Precache()
  154. {
  155. PrecacheParticleSystem( g_pszEnergyProjectileImpactParticle );
  156. PrecacheParticleSystem( g_pszBisonTrailParticle );
  157. PrecacheParticleSystem( g_pszBisonTrailParticleCrit );
  158. PrecacheScriptSound( g_pszBisonImpactWorldSound );
  159. PrecacheScriptSound( g_pszBisonImpactFleshSound );
  160. PrecacheParticleSystem( g_pszPomsonTrailParticle );
  161. PrecacheParticleSystem( g_pszPomsonTrailParticleCrit );
  162. PrecacheScriptSound( g_pszPomsonImpactWorldSound );
  163. PrecacheScriptSound( g_pszPomsonImpactFleshSound );
  164. BaseClass::Precache();
  165. }
  166. #ifdef GAME_DLL
  167. struct collidelist_t
  168. {
  169. const CPhysCollide *pCollide;
  170. Vector origin;
  171. QAngle angles;
  172. };
  173. //-----------------------------------------------------------------------------
  174. // Purpose:
  175. //-----------------------------------------------------------------------------
  176. void CTFProjectile_EnergyRing::ProjectileTouch( CBaseEntity *pOther )
  177. {
  178. // Verify a correct "other."
  179. Assert( pOther );
  180. if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) || pOther->IsSolidFlagSet( FSOLID_NOT_SOLID )
  181. || pOther->GetCollisionGroup() == TFCOLLISION_GROUP_RESPAWNROOMS )
  182. return;
  183. CBaseEntity* pOwner = GetOwnerEntity();
  184. // Don't shoot ourselves
  185. if ( pOwner == pOther )
  186. return;
  187. // Handle hitting skybox (disappear).
  188. const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
  189. if( pTrace->surface.flags & SURF_SKY )
  190. {
  191. UTIL_Remove( this );
  192. return;
  193. }
  194. // pass through ladders
  195. if( pTrace->surface.flags & CONTENTS_LADDER )
  196. return;
  197. // Used when checking against things like FUNC_BRUSHES
  198. if ( !pOther->IsWorld() && pOther->GetSolid() == SOLID_VPHYSICS )
  199. {
  200. CPhysCollide *pTriggerCollide = modelinfo->GetVCollide( GetModelIndex() )->solids[0];
  201. Assert( pTriggerCollide );
  202. CUtlVector<collidelist_t> collideList;
  203. IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
  204. int physicsCount = pOther->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
  205. vcollide_t *pVCollide = modelinfo->GetVCollide( pOther->GetModelIndex() );
  206. if ( physicsCount )
  207. {
  208. for ( int i = 0; i < physicsCount; i++ )
  209. {
  210. const CPhysCollide *pCollide = pList[i]->GetCollide();
  211. if ( pCollide )
  212. {
  213. collidelist_t element;
  214. element.pCollide = pCollide;
  215. pList[i]->GetPosition( &element.origin, &element.angles );
  216. collideList.AddToTail( element );
  217. }
  218. }
  219. }
  220. else if ( pVCollide && pVCollide->solidCount )
  221. {
  222. collidelist_t element;
  223. element.pCollide = pVCollide->solids[0];
  224. element.origin = pOther->GetAbsOrigin();
  225. element.angles = pOther->GetAbsAngles();
  226. collideList.AddToTail( element );
  227. }
  228. else
  229. {
  230. return;
  231. }
  232. for ( int i = collideList.Count()-1; i >= 0; --i )
  233. {
  234. const collidelist_t &element = collideList[i];
  235. trace_t tr;
  236. physcollision->TraceCollide( pTrace->startpos, element.origin, element.pCollide, element.angles, pTriggerCollide, GetAbsOrigin(), GetAbsAngles(), &tr );
  237. if ( !tr.DidHit() )
  238. return;
  239. }
  240. }
  241. // The stuff we collide with
  242. bool bCombatEntity = pOther->IsPlayer() ||
  243. pOther->IsBaseObject() ||
  244. pOther->IsCombatCharacter() ||
  245. pOther->IsCombatItem();
  246. if ( !bCombatEntity )
  247. {
  248. // Couple more things that we collide with
  249. // HACK: these are the same checks we do in CTFProjectile_Arrow::ArrowTouch()...need to figure out a better way to do this when we have time
  250. CTFPumpkinBomb *pPumpkinBomb = dynamic_cast<CTFPumpkinBomb*>( pOther );
  251. CTFMerasmusTrickOrTreatProp *pMerasmusProp = dynamic_cast<CTFMerasmusTrickOrTreatProp*>( pOther );
  252. CTFRobotDestruction_Robot *pRobot = dynamic_cast<CTFRobotDestruction_Robot*>( pOther );
  253. if ( pPumpkinBomb || pMerasmusProp || pRobot )
  254. {
  255. bCombatEntity = true;
  256. }
  257. }
  258. if ( bCombatEntity && ( pOther != pOwner ) )
  259. {
  260. // Bison projectiles shouldn't collide with friendly things
  261. if ( pOther->GetTeamNumber() == GetTeamNumber() && ShouldPenetrate() )
  262. {
  263. return;
  264. }
  265. FOR_EACH_VEC( m_vecHitEnemies, i )
  266. {
  267. // Check if we've already damaged this entity. If so, don't do it again
  268. if ( m_vecHitEnemies[i] == pOther )
  269. return;
  270. }
  271. const int nMaxPenetrates = 5;
  272. const int nDamage = GetDamage() * pow( 0.75f, m_vecHitEnemies.Count() );
  273. CTakeDamageInfo info( this, pOwner, GetLauncher(), nDamage, GetDamageType(), TF_DMG_CUSTOM_PLASMA );
  274. info.SetReportedPosition( pOwner->GetAbsOrigin() );
  275. info.SetDamagePosition( pTrace->endpos );
  276. if ( info.GetDamageType() & DMG_CRITICAL )
  277. {
  278. info.SetCritType( CTakeDamageInfo::CRIT_FULL );
  279. }
  280. trace_t traceAttack;
  281. UTIL_TraceLine( WorldSpaceCenter(), pOther->WorldSpaceCenter(), MASK_SOLID|CONTENTS_HITBOX, this, COLLISION_GROUP_NONE, &traceAttack );
  282. pOther->DispatchTraceAttack( info, GetAbsVelocity(), &traceAttack );
  283. ApplyMultiDamage();
  284. m_vecHitEnemies.AddToTail( pOther );
  285. // Get a position on whatever we hit
  286. Vector vecDelta = pOther->GetAbsOrigin() - GetAbsOrigin();
  287. Vector vecNormalVel = GetAbsVelocity().Normalized();
  288. Vector vecNewPos = ( DotProduct( vecDelta, vecNormalVel ) * vecNormalVel ) + GetAbsOrigin();
  289. PlayImpactEffects( vecNewPos, pOther->IsPlayer() );
  290. int iPenetrate = 0;
  291. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iPenetrate, energy_weapon_penetration );
  292. if ( iPenetrate && m_vecHitEnemies.Count() < nMaxPenetrates )
  293. {
  294. return;
  295. }
  296. UTIL_Remove( this );
  297. return;
  298. }
  299. if ( pOther->IsWorld() )
  300. {
  301. SetAbsVelocity( vec3_origin );
  302. AddSolidFlags( FSOLID_NOT_SOLID );
  303. }
  304. PlayImpactEffects( pTrace->endpos, false );
  305. // Remove by default. Fixes this entity living forever on things like doors.
  306. UTIL_Remove( this );
  307. }
  308. void CTFProjectile_EnergyRing::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity )
  309. {
  310. PlayImpactEffects( trace.endpos, false );
  311. // Remove by default. Fixes this entity living forever on things like doors.
  312. UTIL_Remove( this );
  313. }
  314. void CTFProjectile_EnergyRing::PlayImpactEffects( const Vector& vecPos, bool bHitFlesh )
  315. {
  316. CTFWeaponBaseGun* pTFGun = dynamic_cast< CTFWeaponBaseGun* >( GetLauncher() );
  317. if ( pTFGun )
  318. {
  319. DispatchParticleEffect( g_pszEnergyProjectileImpactParticle, vecPos, GetAbsAngles(), pTFGun->GetParticleColor( 1 ), pTFGun->GetParticleColor( 2 ), true, NULL, 0 );
  320. const char* pszSoundString = NULL;
  321. if ( ShouldPenetrate() )
  322. {
  323. pszSoundString = bHitFlesh ? g_pszBisonImpactFleshSound : g_pszBisonImpactWorldSound;
  324. }
  325. else
  326. {
  327. pszSoundString = bHitFlesh ? g_pszPomsonImpactFleshSound : g_pszPomsonImpactWorldSound;
  328. }
  329. EmitSound( pszSoundString );
  330. }
  331. }
  332. #else
  333. void CTFProjectile_EnergyRing::OnDataChanged( DataUpdateType_t updateType )
  334. {
  335. BaseClass::OnDataChanged( updateType );
  336. if ( updateType == DATA_UPDATE_CREATED )
  337. {
  338. CNewParticleEffect* pEffect = ParticleProp()->Create( GetTrailParticleName(), PATTACH_ABSORIGIN_FOLLOW );
  339. CTFWeaponBaseGun* pTFGun = dynamic_cast< CTFWeaponBaseGun* >( GetLauncher() );
  340. if ( pEffect && pTFGun )
  341. {
  342. pEffect->SetControlPoint( CUSTOM_COLOR_CP1, pTFGun->GetParticleColor( 0 ) );
  343. pEffect->SetControlPoint( CUSTOM_COLOR_CP2, pTFGun->GetParticleColor( 1 ) );
  344. }
  345. }
  346. }
  347. #endif
  348. //-----------------------------------------------------------------------------
  349. // Purpose:
  350. //-----------------------------------------------------------------------------
  351. float CTFProjectile_EnergyRing::GetDamage()
  352. {
  353. return ShouldPenetrate() ? 45.f : 60.f;
  354. }
  355. bool CTFProjectile_EnergyRing::ShouldPenetrate() const
  356. {
  357. int iPenetrate = 0;
  358. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iPenetrate, energy_weapon_penetration );
  359. return iPenetrate != 0;
  360. }
  361. const char* CTFProjectile_EnergyRing::GetTrailParticleName() const
  362. {
  363. if ( ShouldPenetrate() ) // Righteous Bison
  364. {
  365. return IsCritical() ? g_pszBisonTrailParticleCrit : g_pszBisonTrailParticle;
  366. }
  367. else // Pomson
  368. {
  369. return IsCritical() ? g_pszPomsonTrailParticleCrit : g_pszPomsonTrailParticle;
  370. }
  371. }
  372. //-----------------------------------------------------------------------------
  373. // The following is legacy code to support old demos
  374. //-----------------------------------------------------------------------------
  375. #ifdef CLIENT_DLL
  376. void CreateClientSideEnergyRing( const char* pszStandardParticle, const char* pszCritParticle, const CEffectData &data, int nFlags )
  377. {
  378. C_BaseEntity *entity = ClientEntityList().GetBaseEntityFromHandle( data.m_hEntity );
  379. if ( !entity )
  380. {
  381. return;
  382. }
  383. C_TFPlayer *pPlayer = dynamic_cast< C_TFPlayer * >( entity->GetOwnerEntity() );
  384. if ( pPlayer )
  385. {
  386. C_LocalTempEntity *pRing = ClientsideProjectileCallback( data, 0.f );
  387. if ( pRing )
  388. {
  389. bool bCritical = ( ( data.m_nDamageType & DMG_CRITICAL ) != 0 );
  390. CNewParticleEffect* pEffect = pRing->AddParticleEffect( bCritical ? pszCritParticle : pszStandardParticle );
  391. if ( pEffect )
  392. {
  393. pEffect->SetControlPoint( CUSTOM_COLOR_CP1, data.m_CustomColors.m_vecColor1 );
  394. pEffect->SetControlPoint( CUSTOM_COLOR_CP2, data.m_CustomColors.m_vecColor2 );
  395. }
  396. pRing->AddEffects( EF_NOSHADOW );
  397. pRing->flags = nFlags;
  398. pRing->SetRenderMode( kRenderNone );
  399. pRing->SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
  400. pRing->SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS );
  401. }
  402. }
  403. }
  404. //-----------------------------------------------------------------------------
  405. // Purpose: Bison effect callback
  406. //-----------------------------------------------------------------------------
  407. void ClientsideProjectileRingCallback( const CEffectData &data )
  408. {
  409. CreateClientSideEnergyRing( g_pszBisonTrailParticle, g_pszBisonTrailParticleCrit, data, FTENT_COLLIDEKILL | FTENT_COLLIDEPROPS | FTENT_ATTACHTOTARGET | FTENT_ALIGNTOMOTION | FTENT_CLIENTSIDEPARTICLES );
  410. }
  411. DECLARE_CLIENT_EFFECT( ENERGY_RING_DISPATCH_EFFECT, ClientsideProjectileRingCallback );
  412. //-----------------------------------------------------------------------------
  413. // Purpose: Pomson effect callback
  414. //-----------------------------------------------------------------------------
  415. void ClientsideProjectileRingPomsonCallback( const CEffectData &data )
  416. {
  417. CreateClientSideEnergyRing( g_pszPomsonTrailParticle, g_pszPomsonTrailParticleCrit, data, FTENT_COLLIDEALL | FTENT_USEFASTCOLLISIONS | FTENT_ATTACHTOTARGET | FTENT_ALIGNTOMOTION | FTENT_CLIENTSIDEPARTICLES );
  418. }
  419. DECLARE_CLIENT_EFFECT( ENERGY_RING_DISPATCH_EFFECT_POMSON, ClientsideProjectileRingPomsonCallback );
  420. #endif