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.

447 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Implements a sniper rifle weapon.
  4. //
  5. // Primary attack: fires a single high-powered shot, then reloads.
  6. // Secondary attack: cycles sniper scope through zoom levels.
  7. //
  8. // TODO: Circular mask around crosshairs when zoomed in.
  9. // TODO: Shell ejection.
  10. // TODO: Finalize kickback.
  11. // TODO: Animated zoom effect?
  12. //
  13. //=============================================================================//
  14. #include "cbase.h"
  15. #include "npcevent.h"
  16. #include "basehlcombatweapon.h"
  17. #include "basecombatcharacter.h"
  18. #include "ai_basenpc.h"
  19. #include "player.h"
  20. #include "gamerules.h" // For g_pGameRules
  21. #include "in_buttons.h"
  22. #include "soundent.h"
  23. #include "vstdlib/random.h"
  24. // memdbgon must be the last include file in a .cpp file!!!
  25. #include "tier0/memdbgon.h"
  26. #define SNIPER_CONE_PLAYER vec3_origin // Spread cone when fired by the player.
  27. #define SNIPER_CONE_NPC vec3_origin // Spread cone when fired by NPCs.
  28. #define SNIPER_BULLET_COUNT_PLAYER 1 // Fire n bullets per shot fired by the player.
  29. #define SNIPER_BULLET_COUNT_NPC 1 // Fire n bullets per shot fired by NPCs.
  30. #define SNIPER_TRACER_FREQUENCY_PLAYER 0 // Draw a tracer every nth shot fired by the player.
  31. #define SNIPER_TRACER_FREQUENCY_NPC 0 // Draw a tracer every nth shot fired by NPCs.
  32. #define SNIPER_KICKBACK 3 // Range for punchangle when firing.
  33. #define SNIPER_ZOOM_RATE 0.2 // Interval between zoom levels in seconds.
  34. //-----------------------------------------------------------------------------
  35. // Discrete zoom levels for the scope.
  36. //-----------------------------------------------------------------------------
  37. static int g_nZoomFOV[] =
  38. {
  39. 20,
  40. 5
  41. };
  42. class CWeaponSniperRifle : public CBaseHLCombatWeapon
  43. {
  44. DECLARE_DATADESC();
  45. public:
  46. DECLARE_CLASS( CWeaponSniperRifle, CBaseHLCombatWeapon );
  47. CWeaponSniperRifle(void);
  48. DECLARE_SERVERCLASS();
  49. void Precache( void );
  50. int CapabilitiesGet( void ) const;
  51. const Vector &GetBulletSpread( void );
  52. bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
  53. void ItemPostFrame( void );
  54. void PrimaryAttack( void );
  55. bool Reload( void );
  56. void Zoom( void );
  57. virtual float GetFireRate( void ) { return 1; };
  58. void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
  59. DECLARE_ACTTABLE();
  60. protected:
  61. float m_fNextZoom;
  62. int m_nZoomLevel;
  63. };
  64. IMPLEMENT_SERVERCLASS_ST(CWeaponSniperRifle, DT_WeaponSniperRifle)
  65. END_SEND_TABLE()
  66. LINK_ENTITY_TO_CLASS( weapon_sniperrifle, CWeaponSniperRifle );
  67. PRECACHE_WEAPON_REGISTER(weapon_sniperrifle);
  68. BEGIN_DATADESC( CWeaponSniperRifle )
  69. DEFINE_FIELD( m_fNextZoom, FIELD_FLOAT ),
  70. DEFINE_FIELD( m_nZoomLevel, FIELD_INTEGER ),
  71. END_DATADESC()
  72. //-----------------------------------------------------------------------------
  73. // Maps base activities to weapons-specific ones so our characters do the right things.
  74. //-----------------------------------------------------------------------------
  75. acttable_t CWeaponSniperRifle::m_acttable[] =
  76. {
  77. { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SNIPER_RIFLE, true }
  78. };
  79. IMPLEMENT_ACTTABLE(CWeaponSniperRifle);
  80. //-----------------------------------------------------------------------------
  81. // Purpose: Constructor.
  82. //-----------------------------------------------------------------------------
  83. CWeaponSniperRifle::CWeaponSniperRifle( void )
  84. {
  85. m_fNextZoom = gpGlobals->curtime;
  86. m_nZoomLevel = 0;
  87. m_bReloadsSingly = true;
  88. m_fMinRange1 = 65;
  89. m_fMinRange2 = 65;
  90. m_fMaxRange1 = 2048;
  91. m_fMaxRange2 = 2048;
  92. }
  93. //-----------------------------------------------------------------------------
  94. // Purpose:
  95. // Output : int
  96. //-----------------------------------------------------------------------------
  97. int CWeaponSniperRifle::CapabilitiesGet( void ) const
  98. {
  99. return bits_CAP_WEAPON_RANGE_ATTACK1;
  100. }
  101. //-----------------------------------------------------------------------------
  102. // Purpose: Turns off the zoom when the rifle is holstered.
  103. //-----------------------------------------------------------------------------
  104. bool CWeaponSniperRifle::Holster( CBaseCombatWeapon *pSwitchingTo )
  105. {
  106. CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
  107. if (pPlayer != NULL)
  108. {
  109. if ( m_nZoomLevel != 0 )
  110. {
  111. if ( pPlayer->SetFOV( this, 0 ) )
  112. {
  113. pPlayer->ShowViewModel(true);
  114. m_nZoomLevel = 0;
  115. }
  116. }
  117. }
  118. return BaseClass::Holster(pSwitchingTo);
  119. }
  120. //-----------------------------------------------------------------------------
  121. // Purpose: Overloaded to handle the zoom functionality.
  122. //-----------------------------------------------------------------------------
  123. void CWeaponSniperRifle::ItemPostFrame( void )
  124. {
  125. CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
  126. if (pPlayer == NULL)
  127. {
  128. return;
  129. }
  130. if ((m_bInReload) && (m_flNextPrimaryAttack <= gpGlobals->curtime))
  131. {
  132. FinishReload();
  133. m_bInReload = false;
  134. }
  135. if (pPlayer->m_nButtons & IN_ATTACK2)
  136. {
  137. if (m_fNextZoom <= gpGlobals->curtime)
  138. {
  139. Zoom();
  140. pPlayer->m_nButtons &= ~IN_ATTACK2;
  141. }
  142. }
  143. else if ((pPlayer->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime))
  144. {
  145. if ( (m_iClip1 == 0 && UsesClipsForAmmo1()) || ( !UsesClipsForAmmo1() && !pPlayer->GetAmmoCount(m_iPrimaryAmmoType) ) )
  146. {
  147. m_bFireOnEmpty = true;
  148. }
  149. // Fire underwater?
  150. if (pPlayer->GetWaterLevel() == 3 && m_bFiresUnderwater == false)
  151. {
  152. WeaponSound(EMPTY);
  153. m_flNextPrimaryAttack = gpGlobals->curtime + 0.2;
  154. return;
  155. }
  156. else
  157. {
  158. // If the firing button was just pressed, reset the firing time
  159. if ( pPlayer && pPlayer->m_afButtonPressed & IN_ATTACK )
  160. {
  161. m_flNextPrimaryAttack = gpGlobals->curtime;
  162. }
  163. PrimaryAttack();
  164. }
  165. }
  166. // -----------------------
  167. // Reload pressed / Clip Empty
  168. // -----------------------
  169. if ( pPlayer->m_nButtons & IN_RELOAD && UsesClipsForAmmo1() && !m_bInReload )
  170. {
  171. // reload when reload is pressed, or if no buttons are down and weapon is empty.
  172. Reload();
  173. }
  174. // -----------------------
  175. // No buttons down
  176. // -----------------------
  177. if (!((pPlayer->m_nButtons & IN_ATTACK) || (pPlayer->m_nButtons & IN_ATTACK2) || (pPlayer->m_nButtons & IN_RELOAD)))
  178. {
  179. // no fire buttons down
  180. m_bFireOnEmpty = false;
  181. if ( !HasAnyAmmo() && m_flNextPrimaryAttack < gpGlobals->curtime )
  182. {
  183. // weapon isn't useable, switch.
  184. if ( !(GetWeaponFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && pPlayer->SwitchToNextBestWeapon( this ) )
  185. {
  186. m_flNextPrimaryAttack = gpGlobals->curtime + 0.3;
  187. return;
  188. }
  189. }
  190. else
  191. {
  192. // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing
  193. if ( m_iClip1 == 0 && !(GetWeaponFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < gpGlobals->curtime )
  194. {
  195. Reload();
  196. return;
  197. }
  198. }
  199. WeaponIdle( );
  200. return;
  201. }
  202. }
  203. //-----------------------------------------------------------------------------
  204. // Purpose:
  205. //-----------------------------------------------------------------------------
  206. void CWeaponSniperRifle::Precache( void )
  207. {
  208. BaseClass::Precache();
  209. }
  210. //-----------------------------------------------------------------------------
  211. // Purpose: Same as base reload but doesn't change the owner's next attack time. This
  212. // lets us zoom out while reloading. This hack is necessary because our
  213. // ItemPostFrame is only called when the owner's next attack time has
  214. // expired.
  215. // Output : Returns true if the weapon was reloaded, false if no more ammo.
  216. //-----------------------------------------------------------------------------
  217. bool CWeaponSniperRifle::Reload( void )
  218. {
  219. CBaseCombatCharacter *pOwner = GetOwner();
  220. if (!pOwner)
  221. {
  222. return false;
  223. }
  224. if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) > 0)
  225. {
  226. int primary = MIN(GetMaxClip1() - m_iClip1, pOwner->GetAmmoCount(m_iPrimaryAmmoType));
  227. int secondary = MIN(GetMaxClip2() - m_iClip2, pOwner->GetAmmoCount(m_iSecondaryAmmoType));
  228. if (primary > 0 || secondary > 0)
  229. {
  230. // Play reload on different channel as it happens after every fire
  231. // and otherwise steals channel away from fire sound
  232. WeaponSound(RELOAD);
  233. SendWeaponAnim( ACT_VM_RELOAD );
  234. m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
  235. m_bInReload = true;
  236. }
  237. return true;
  238. }
  239. return false;
  240. }
  241. //-----------------------------------------------------------------------------
  242. // Purpose:
  243. //-----------------------------------------------------------------------------
  244. void CWeaponSniperRifle::PrimaryAttack( void )
  245. {
  246. // Only the player fires this way so we can cast safely.
  247. CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
  248. if (!pPlayer)
  249. {
  250. return;
  251. }
  252. if ( gpGlobals->curtime >= m_flNextPrimaryAttack )
  253. {
  254. // If my clip is empty (and I use clips) start reload
  255. if ( !m_iClip1 )
  256. {
  257. Reload();
  258. return;
  259. }
  260. // MUST call sound before removing a round from the clip of a CMachineGun dvs: does this apply to the sniper rifle? I don't know.
  261. WeaponSound(SINGLE);
  262. pPlayer->DoMuzzleFlash();
  263. SendWeaponAnim( ACT_VM_PRIMARYATTACK );
  264. // player "shoot" animation
  265. pPlayer->SetAnimation( PLAYER_ATTACK1 );
  266. // Don't fire again until fire animation has completed
  267. m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
  268. m_iClip1 = m_iClip1 - 1;
  269. Vector vecSrc = pPlayer->Weapon_ShootPosition();
  270. Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES );
  271. // Fire the bullets
  272. pPlayer->FireBullets( SNIPER_BULLET_COUNT_PLAYER, vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, SNIPER_TRACER_FREQUENCY_PLAYER );
  273. CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 600, 0.2 );
  274. QAngle vecPunch(random->RandomFloat( -SNIPER_KICKBACK, SNIPER_KICKBACK ), 0, 0);
  275. pPlayer->ViewPunch(vecPunch);
  276. // Indicate out of ammo condition if we run out of ammo.
  277. if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0)
  278. {
  279. pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0);
  280. }
  281. }
  282. // Register a muzzleflash for the AI.
  283. pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 );
  284. }
  285. //-----------------------------------------------------------------------------
  286. // Purpose: Zooms in using the sniper rifle scope.
  287. //-----------------------------------------------------------------------------
  288. void CWeaponSniperRifle::Zoom( void )
  289. {
  290. CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
  291. if (!pPlayer)
  292. {
  293. return;
  294. }
  295. if (m_nZoomLevel >= sizeof(g_nZoomFOV) / sizeof(g_nZoomFOV[0]))
  296. {
  297. if ( pPlayer->SetFOV( this, 0 ) )
  298. {
  299. pPlayer->ShowViewModel(true);
  300. // Zoom out to the default zoom level
  301. WeaponSound(SPECIAL2);
  302. m_nZoomLevel = 0;
  303. }
  304. }
  305. else
  306. {
  307. if ( pPlayer->SetFOV( this, g_nZoomFOV[m_nZoomLevel] ) )
  308. {
  309. if (m_nZoomLevel == 0)
  310. {
  311. pPlayer->ShowViewModel(false);
  312. }
  313. WeaponSound(SPECIAL1);
  314. m_nZoomLevel++;
  315. }
  316. }
  317. m_fNextZoom = gpGlobals->curtime + SNIPER_ZOOM_RATE;
  318. }
  319. //-----------------------------------------------------------------------------
  320. // Purpose:
  321. // Output : virtual const Vector&
  322. //-----------------------------------------------------------------------------
  323. const Vector &CWeaponSniperRifle::GetBulletSpread( void )
  324. {
  325. static Vector cone = SNIPER_CONE_PLAYER;
  326. return cone;
  327. }
  328. //-----------------------------------------------------------------------------
  329. // Purpose:
  330. // Input : *pEvent -
  331. // *pOperator -
  332. //-----------------------------------------------------------------------------
  333. void CWeaponSniperRifle::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
  334. {
  335. switch ( pEvent->event )
  336. {
  337. case EVENT_WEAPON_SNIPER_RIFLE_FIRE:
  338. {
  339. Vector vecShootOrigin, vecShootDir;
  340. vecShootOrigin = pOperator->Weapon_ShootPosition();
  341. CAI_BaseNPC *npc = pOperator->MyNPCPointer();
  342. Vector vecSpread;
  343. if (npc)
  344. {
  345. vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin );
  346. vecSpread = VECTOR_CONE_PRECALCULATED;
  347. }
  348. else
  349. {
  350. AngleVectors( pOperator->GetLocalAngles(), &vecShootDir );
  351. vecSpread = GetBulletSpread();
  352. }
  353. WeaponSound( SINGLE_NPC );
  354. pOperator->FireBullets( SNIPER_BULLET_COUNT_NPC, vecShootOrigin, vecShootDir, vecSpread, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, SNIPER_TRACER_FREQUENCY_NPC );
  355. pOperator->DoMuzzleFlash();
  356. break;
  357. }
  358. default:
  359. {
  360. BaseClass::Operator_HandleAnimEvent( pEvent, pOperator );
  361. break;
  362. }
  363. }
  364. }