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.

536 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "tf_weapon_mechanical_arm.h"
  8. #include "in_buttons.h"
  9. #if !defined( CLIENT_DLL )
  10. #include "tf_player.h"
  11. #include "tf_gamestats.h"
  12. #include "ilagcompensationmanager.h"
  13. #include "particle_parse.h"
  14. #include "tf_fx.h"
  15. #include "tf_weapon_grenade_pipebomb.h"
  16. #include "tf_team.h"
  17. #include "tf_passtime_logic.h"
  18. #else
  19. #include "c_tf_player.h"
  20. #endif
  21. //=============================================================================
  22. //
  23. // tables.
  24. //
  25. IMPLEMENT_NETWORKCLASS_ALIASED( TFMechanicalArm, DT_TFMechanicalArm )
  26. BEGIN_NETWORK_TABLE( CTFMechanicalArm, DT_TFMechanicalArm )
  27. END_NETWORK_TABLE()
  28. BEGIN_PREDICTION_DATA( CTFMechanicalArm )
  29. END_PREDICTION_DATA()
  30. LINK_ENTITY_TO_CLASS( tf_weapon_mechanical_arm, CTFMechanicalArm );
  31. PRECACHE_WEAPON_REGISTER( tf_weapon_mechanical_arm );
  32. #define AMMO_BASE_PROJECTILE_SHOCK 10
  33. #define AMMO_PER_PROJECTILE_SHOCK 5
  34. //=============================================================================
  35. //
  36. // CTFMechanicalArm
  37. //
  38. //-----------------------------------------------------------------------------
  39. // Purpose:
  40. //-----------------------------------------------------------------------------
  41. CTFMechanicalArm::CTFMechanicalArm()
  42. {
  43. #ifdef CLIENT_DLL
  44. m_pParticleBeamEffect = NULL;
  45. m_pParticleBeamSpark = NULL;
  46. m_pEffectOwner = NULL;
  47. #endif // CLIENT_DLL
  48. }
  49. CTFMechanicalArm::~CTFMechanicalArm()
  50. {
  51. #ifdef CLIENT_DLL
  52. if ( m_pEffectOwner )
  53. {
  54. if ( m_pParticleBeamEffect )
  55. {
  56. m_pEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pParticleBeamEffect );
  57. m_pParticleBeamEffect = NULL;
  58. }
  59. if ( m_pParticleBeamSpark )
  60. {
  61. m_pEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pParticleBeamSpark );
  62. m_pParticleBeamSpark = NULL;
  63. }
  64. m_pEffectOwner = NULL;
  65. }
  66. #endif // CLIENT_DLL
  67. }
  68. //-----------------------------------------------------------------------------
  69. // Purpose:
  70. //-----------------------------------------------------------------------------
  71. void CTFMechanicalArm::Precache()
  72. {
  73. BaseClass::Precache();
  74. PrecacheParticleSystem( "dxhr_arm_muzzleflash" );
  75. PrecacheParticleSystem( "dxhr_arm_muzzleflash2" );
  76. PrecacheParticleSystem( "dxhr_arm_impact" );
  77. }
  78. //-----------------------------------------------------------------------------
  79. // Purpose:
  80. //-----------------------------------------------------------------------------
  81. bool CTFMechanicalArm::ShockAttack( void )
  82. {
  83. CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() );
  84. if ( !pOwner )
  85. return false;
  86. if ( pOwner->GetWaterLevel() == WL_Eyes )
  87. return false;
  88. // Enough ammo to shock at least one target?
  89. if ( pOwner->GetAmmoCount( m_iPrimaryAmmoType ) < ( AMMO_BASE_PROJECTILE_SHOCK + AMMO_PER_PROJECTILE_SHOCK ) )
  90. return false;
  91. #ifdef GAME_DLL
  92. if ( pOwner->m_Shared.IsStealthed() )
  93. {
  94. pOwner->RemoveInvisibility();
  95. }
  96. lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() );
  97. Vector vecEye = pOwner->EyePosition();
  98. Vector vecForward, vecRight, vecUp;
  99. AngleVectors( pOwner->EyeAngles(), &vecForward, &vecRight, &vecUp );
  100. Vector vecSize = Vector( 128, 128, 64 );
  101. float flMaxElement = 0.0f;
  102. for ( int i = 0; i < 3; ++i )
  103. {
  104. flMaxElement = MAX( flMaxElement, vecSize[i] );
  105. }
  106. Vector vecCenter = vecEye + vecForward * flMaxElement;
  107. CUtlVector< CBaseEntity* > vecShockTargets;
  108. // Get a list of entities in the box defined by vecSize at VecCenter.
  109. // We will then try to deflect everything in the box.
  110. const int maxCollectedEntities = 64;
  111. CBaseEntity *pObjects[ maxCollectedEntities ];
  112. int count = UTIL_EntitiesInBox( pObjects, maxCollectedEntities, vecCenter - vecSize, vecCenter + vecSize, FL_GRENADE | FL_CLIENT | FL_FAKECLIENT );
  113. for ( int i = 0; i < count; i++ )
  114. {
  115. if ( IsValidVictim( pOwner, pObjects[i] ) )
  116. {
  117. vecShockTargets.AddToTail( pObjects[i] );
  118. }
  119. }
  120. // Remove the base cost for attempting to fire, regardless of what we hit
  121. pOwner->RemoveAmmo( AMMO_BASE_PROJECTILE_SHOCK, m_iPrimaryAmmoType );
  122. // We attacked by didn't hit anything
  123. if ( vecShockTargets.Count() == 0 )
  124. {
  125. lagcompensation->FinishLagCompensation( pOwner );
  126. return true;
  127. }
  128. // Horribly hacked muzzle position
  129. Vector vForward, vRight, vUp;
  130. AngleVectors( pOwner->EyeAngles(), &vForward, &vRight, &vUp );
  131. Vector vStart = pOwner->EyePosition()
  132. + (vForward * 40.0f)
  133. + (vRight * 15.0f)
  134. + (vUp * -10.0f);
  135. FOR_EACH_VEC( vecShockTargets, i )
  136. {
  137. if ( pOwner->GetAmmoCount( m_iPrimaryAmmoType ) < AMMO_PER_PROJECTILE_SHOCK )
  138. break;
  139. CBaseEntity* pTarget = vecShockTargets[i];
  140. Vector vecTarget = pTarget->WorldSpaceCenter();
  141. ShockVictim( pOwner, pTarget );
  142. pOwner->RemoveAmmo( AMMO_PER_PROJECTILE_SHOCK, m_iPrimaryAmmoType );
  143. // Play an effect where the target is
  144. CPVSFilter filter( vecTarget );
  145. const char *shootsound = GetShootSound( SPECIAL3 );
  146. if ( shootsound && *shootsound )
  147. {
  148. EmitSound( shootsound );
  149. }
  150. // play the electrical effect from the gun to the pipes
  151. te_tf_particle_effects_control_point_t controlPoint = { PATTACH_WORLDORIGIN, vecTarget };
  152. TE_TFParticleEffectComplex( filter, 0.0f, "dxhr_arm_muzzleflash", vStart, QAngle( 0, 0, 0 ), NULL, &controlPoint, pTarget, PATTACH_CUSTOMORIGIN );
  153. }
  154. lagcompensation->FinishLagCompensation( pOwner );
  155. #endif
  156. return true;
  157. }
  158. #ifdef GAME_DLL
  159. //-----------------------------------------------------------------------------
  160. bool CTFMechanicalArm::IsValidVictim( CTFPlayer *pOwner, CBaseEntity *pTarget )
  161. {
  162. if ( pTarget == NULL )
  163. return false;
  164. if ( pTarget == pOwner )
  165. return false;
  166. if ( pTarget->IsPlayer() && pTarget->GetTeamNumber() == TEAM_SPECTATOR )
  167. return false;
  168. if ( pTarget->GetTeamNumber() == pOwner->GetTeamNumber() )
  169. return false;
  170. if ( pTarget->IsPlayer() && !pTarget->IsAlive() )
  171. return false;
  172. if ( !pTarget->IsDeflectable() && !FClassnameIs( pTarget, "prop_physics" ) )
  173. return false;
  174. if ( pOwner->FVisible( pTarget, MASK_SOLID_BRUSHONLY ) == false )
  175. return false;
  176. if ( g_pPasstimeLogic && ( g_pPasstimeLogic->GetBall() == pTarget ) )
  177. return false;
  178. return true;
  179. }
  180. //-----------------------------------------------------------------------------
  181. void CTFMechanicalArm::ShockVictim( CTFPlayer *pOwner, CBaseEntity *pTarget )
  182. {
  183. // Projectile
  184. if ( !pTarget->IsPlayer() )
  185. {
  186. pTarget->SetThink( &BaseClass::SUB_Remove );
  187. pTarget->SetNextThink( gpGlobals->curtime );
  188. pTarget->SetTouch( NULL );
  189. pTarget->AddEffects( EF_NODRAW );
  190. pTarget->RemoveFlag( FL_GRENADE );
  191. }
  192. // deal damage
  193. CTakeDamageInfo info;
  194. info.SetDamageType( DMG_SHOCK );
  195. info.SetAttacker( pOwner );
  196. info.SetInflictor( this );
  197. info.SetWeapon( this );
  198. info.SetDamage( 20 );
  199. info.SetDamagePosition( pTarget->WorldSpaceCenter() );
  200. pTarget->TakeDamage( info );
  201. // Achievement
  202. CTFGrenadePipebombProjectile *pPipebomb = dynamic_cast<CTFGrenadePipebombProjectile*>( pTarget );
  203. if ( pPipebomb && pPipebomb->HasStickyEffects() )
  204. {
  205. // If we are near a building, award achievement progress.
  206. CTFTeam *pTeam = pOwner->GetTFTeam();
  207. if ( pTeam )
  208. {
  209. for ( int j = 0; j < pTeam->GetNumObjects(); j++ )
  210. {
  211. CBaseObject *pTemp = pTeam->GetObject( j );
  212. if ( pTemp && ( pTemp->ObjectType() != OBJ_ATTACHMENT_SAPPER ) )
  213. {
  214. if ( ( pTemp->GetAbsOrigin().DistTo( pPipebomb->GetAbsOrigin() ) < 100 ) &&
  215. ( pTemp->FVisible( pPipebomb, MASK_SOLID_BRUSHONLY ) ) )
  216. {
  217. pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DESTROY_STICKIES, 1 );
  218. break; // Only one award per sticky.
  219. }
  220. }
  221. }
  222. }
  223. }
  224. }
  225. #endif
  226. //-----------------------------------------------------------------------------
  227. void CTFMechanicalArm::SecondaryAttack( void )
  228. {
  229. CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() );
  230. if ( !pOwner )
  231. return;
  232. // Are we capable of firing again?
  233. if ( m_flNextSecondaryAttack > gpGlobals->curtime )
  234. return;
  235. if ( m_iPrimaryAmmoType == TF_AMMO_METAL )
  236. {
  237. if ( ( GetAmmoPerShot() > GetOwner()->GetAmmoCount( m_iPrimaryAmmoType ) ) ||
  238. ( pOwner->GetWaterLevel() == WL_Eyes ) )
  239. {
  240. WeaponSound( EMPTY );
  241. m_flNextSecondaryAttack = gpGlobals->curtime + 0.67f;
  242. return;
  243. }
  244. }
  245. if ( !CanAttack() )
  246. return;
  247. if ( ShockAttack() )
  248. {
  249. WeaponSound( SPECIAL3 );
  250. }
  251. else
  252. {
  253. WeaponSound( EMPTY );
  254. }
  255. #ifdef CLIENT_DLL
  256. // Play an effect on the client so they have some kind of visual feedback that something happened
  257. int iParticleAttachment = LookupAttachment( "muzzle" );
  258. CNewParticleEffect* pEffect = ParticleProp()->Create( "dxhr_sniper_fizzle", PATTACH_POINT_FOLLOW, iParticleAttachment );
  259. ParticleProp()->AddControlPoint( pEffect, 1, this, PATTACH_POINT_FOLLOW, "muzzle" );
  260. #endif
  261. SendWeaponAnim( ACT_VM_PRIMARYATTACK );
  262. pOwner->SetAnimation( PLAYER_ATTACK1 );
  263. m_flNextPrimaryAttack = gpGlobals->curtime + 0.67f;
  264. m_flNextSecondaryAttack = gpGlobals->curtime + 0.67f;
  265. }
  266. #ifdef CLIENT_DLL
  267. void CTFMechanicalArm::OnDataChanged( DataUpdateType_t updateType )
  268. {
  269. BaseClass::OnDataChanged( updateType );
  270. UpdateParticleBeam();
  271. }
  272. //-----------------------------------------------------------------------------
  273. void CTFMechanicalArm::StopParticleBeam( void )
  274. {
  275. if ( !m_pEffectOwner )
  276. return;
  277. // Different owners, kill the old effect
  278. if ( m_pParticleBeamEffect )
  279. {
  280. m_pEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pParticleBeamEffect );
  281. m_pParticleBeamEffect = NULL;
  282. }
  283. if ( m_pParticleBeamSpark )
  284. {
  285. m_pEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pParticleBeamSpark );
  286. m_pParticleBeamSpark = NULL;
  287. }
  288. m_pEffectOwner = NULL;
  289. }
  290. //-----------------------------------------------------------------------------
  291. void CTFMechanicalArm::UpdateParticleBeam()
  292. {
  293. // Update Particle
  294. // If we are attacking, update the particle (make it render)
  295. CTFPlayer *pFiringPlayer = ToTFPlayer( GetOwnerEntity() );
  296. if ( !pFiringPlayer )
  297. {
  298. StopParticleBeam();
  299. return;
  300. }
  301. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  302. C_BaseEntity *pEffectOwner = this;
  303. if ( pLocalPlayer == pFiringPlayer )
  304. {
  305. pEffectOwner = pLocalPlayer->GetRenderedWeaponModel();
  306. if ( !pEffectOwner )
  307. {
  308. StopParticleBeam();
  309. return;
  310. }
  311. }
  312. if ( m_pEffectOwner && m_pEffectOwner != pEffectOwner )
  313. {
  314. StopParticleBeam();
  315. return;
  316. }
  317. if ( m_flNextSecondaryAttack > gpGlobals->curtime )
  318. {
  319. StopParticleBeam();
  320. return;
  321. }
  322. m_pEffectOwner = pEffectOwner;
  323. // Constantly perform the shock attack and update control points if attack is down and we've already fired
  324. if ( pFiringPlayer
  325. && pFiringPlayer->m_nButtons & IN_ATTACK
  326. && pFiringPlayer->GetActiveWeapon() == this
  327. && pFiringPlayer->GetWaterLevel() != WL_Eyes
  328. && pFiringPlayer->m_flNextAttack < gpGlobals->curtime )
  329. {
  330. trace_t tr;
  331. Vector vecAiming;
  332. pFiringPlayer->EyeVectors( &vecAiming );
  333. Vector vecEnd = pFiringPlayer->EyePosition() + vecAiming * 256.0f;
  334. UTIL_TraceLine( pFiringPlayer->EyePosition(), vecEnd, ( MASK_SHOT & ~CONTENTS_HITBOX ), pFiringPlayer, DMG_GENERIC, &tr );
  335. // Line laser
  336. if ( !m_pParticleBeamEffect )
  337. {
  338. const char *pszEffectName = "dxhr_arm_muzzleflash2";
  339. m_pParticleBeamEffect = pEffectOwner->ParticleProp()->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" );
  340. }
  341. if ( m_pParticleBeamEffect )
  342. {
  343. Vector vEndPos = tr.endpos - pFiringPlayer->GetAbsOrigin();
  344. pEffectOwner->ParticleProp()->AddControlPoint( m_pParticleBeamEffect, 1, pFiringPlayer, PATTACH_ABSORIGIN_FOLLOW, NULL, vEndPos );
  345. }
  346. // Spark
  347. if ( !m_pParticleBeamSpark && tr.m_pEnt && tr.m_pEnt->IsPlayer( ) )
  348. {
  349. m_pParticleBeamSpark = pEffectOwner->ParticleProp( )->Create( "dxhr_arm_impact", PATTACH_ABSORIGIN_FOLLOW );
  350. }
  351. else if ( m_pParticleBeamSpark && (!tr.m_pEnt || !tr.m_pEnt->IsPlayer() ) )
  352. {
  353. m_pEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pParticleBeamSpark );
  354. m_pParticleBeamSpark = NULL;
  355. }
  356. if ( m_pParticleBeamSpark )
  357. {
  358. Vector vEndPos = tr.endpos - pFiringPlayer->GetAbsOrigin();
  359. pEffectOwner->ParticleProp( )->AddControlPoint( m_pParticleBeamSpark, 1, pFiringPlayer, PATTACH_ABSORIGIN_FOLLOW, NULL, vEndPos );
  360. }
  361. }
  362. else if ( m_pEffectOwner )
  363. {
  364. StopParticleBeam( );
  365. }
  366. }
  367. #endif // CLIENT_DLL
  368. //-----------------------------------------------------------------------------
  369. void CTFMechanicalArm::PrimaryAttack()
  370. {
  371. CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() );
  372. if ( !pOwner )
  373. return;
  374. float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay );
  375. if ( m_iPrimaryAmmoType == TF_AMMO_METAL )
  376. {
  377. if ( ( GetAmmoPerShot() > GetOwner()->GetAmmoCount( m_iPrimaryAmmoType ) ) ||
  378. ( pOwner->GetWaterLevel( ) == WL_Eyes ) )
  379. {
  380. WeaponSound( EMPTY );
  381. m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay;
  382. return;
  383. }
  384. }
  385. // Are we capable of firing again?
  386. if ( m_flNextPrimaryAttack > gpGlobals->curtime )
  387. return;
  388. // Get the player owning the weapon.
  389. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
  390. if ( !pPlayer )
  391. return;
  392. if ( !CanAttack() )
  393. return;
  394. #ifdef GAME_DLL
  395. CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, false );
  396. int iAmmoPerShot = 0;
  397. CALL_ATTRIB_HOOK_INT( iAmmoPerShot, mod_ammo_per_shot );
  398. pOwner->RemoveAmmo( iAmmoPerShot, m_iPrimaryAmmoType );
  399. //int nAmmoToTake = bShocked ? 0 : GetAmmoPerShot();
  400. //pOwner->RemoveAmmo( nAmmoToTake, m_iPrimaryAmmoType );
  401. FireProjectile( pPlayer );
  402. #endif
  403. #ifdef CLIENT_DLL
  404. // Play an effect on the client so they have some kind of visual feedback that something happened
  405. int iParticleAttachment = LookupAttachment( "muzzle" );
  406. CNewParticleEffect* pEffect = ParticleProp()->Create( "dxhr_sniper_fizzle", PATTACH_POINT_FOLLOW, iParticleAttachment );
  407. ParticleProp()->AddControlPoint( pEffect, 1, this, PATTACH_POINT_FOLLOW, "muzzle" );
  408. #endif
  409. WeaponSound( SINGLE );
  410. // Set the weapon mode.
  411. m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
  412. SendWeaponAnim( ACT_VM_PRIMARYATTACK );
  413. pPlayer->SetAnimation( PLAYER_ATTACK1 );
  414. // Set next attack times.
  415. m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay;
  416. SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() );
  417. }
  418. //-----------------------------------------------------------------------------
  419. int CTFMechanicalArm::GetAmmoPerShot( void )
  420. {
  421. // Used by normal fire code, we only decrement ammo on ticks which uses an Attr
  422. return 0;
  423. }
  424. //-----------------------------------------------------------------------------
  425. // Purpose:
  426. //-----------------------------------------------------------------------------
  427. bool CTFMechanicalArm::UpdateBodygroups( CBaseCombatCharacter* pOwner, int iState )
  428. {
  429. if ( !pOwner )
  430. return false;
  431. iState = pOwner->GetActiveWeapon() == this;
  432. bool res = BaseClass::UpdateBodygroups( pOwner, iState );
  433. CTFPlayer *pTFOwner = ToTFPlayer( pOwner );
  434. if ( pTFOwner )
  435. {
  436. CBaseViewModel *pVM = pTFOwner->GetViewModel();
  437. if ( pVM )
  438. {
  439. pVM->SetBodygroup( 1, iState );
  440. }
  441. }
  442. return res;
  443. }