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.

413 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Implements a screen shake effect that can also shake physics objects.
  4. //
  5. // NOTE: UTIL_ScreenShake() will only shake players who are on the ground
  6. //
  7. // $NoKeywords: $
  8. //=============================================================================//
  9. #include "cbase.h"
  10. #include "shake.h"
  11. #include "physics_saverestore.h"
  12. #include "rope.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. class CPhysicsShake : public IMotionEvent
  16. {
  17. DECLARE_SIMPLE_DATADESC();
  18. public:
  19. virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
  20. {
  21. Vector contact;
  22. if ( !pObject->GetContactPoint( &contact, NULL ) )
  23. return SIM_NOTHING;
  24. // fudge the force a bit to make it more dramatic
  25. pObject->CalculateForceOffset( m_force * (1.0f + pObject->GetMass()*0.4f), contact, &linear, &angular );
  26. return SIM_LOCAL_FORCE;
  27. }
  28. Vector m_force;
  29. };
  30. BEGIN_SIMPLE_DATADESC( CPhysicsShake )
  31. DEFINE_FIELD( m_force, FIELD_VECTOR ),
  32. END_DATADESC()
  33. class CEnvShake : public CPointEntity
  34. {
  35. private:
  36. float m_Amplitude;
  37. float m_Frequency;
  38. float m_Duration;
  39. float m_Radius; // radius of 0 means all players
  40. float m_stopTime;
  41. float m_nextShake;
  42. float m_currentAmp;
  43. Vector m_maxForce;
  44. IPhysicsMotionController *m_pShakeController;
  45. CPhysicsShake m_shakeCallback;
  46. DECLARE_DATADESC();
  47. public:
  48. DECLARE_CLASS( CEnvShake, CPointEntity );
  49. ~CEnvShake( void );
  50. virtual void Spawn( void );
  51. virtual void OnRestore( void );
  52. inline float Amplitude( void ) { return m_Amplitude; }
  53. inline float Frequency( void ) { return m_Frequency; }
  54. inline float Duration( void ) { return m_Duration; }
  55. float Radius( bool bPlayers = true );
  56. inline void SetAmplitude( float amplitude ) { m_Amplitude = amplitude; }
  57. inline void SetFrequency( float frequency ) { m_Frequency = frequency; }
  58. inline void SetDuration( float duration ) { m_Duration = duration; }
  59. inline void SetRadius( float radius ) { m_Radius = radius; }
  60. int DrawDebugTextOverlays(void);
  61. // Input handlers
  62. void InputStartShake( inputdata_t &inputdata );
  63. void InputStopShake( inputdata_t &inputdata );
  64. void InputAmplitude( inputdata_t &inputdata );
  65. void InputFrequency( inputdata_t &inputdata );
  66. // Causes the camera/physics shakes to happen:
  67. void ApplyShake( ShakeCommand_t command );
  68. void Think( void );
  69. };
  70. LINK_ENTITY_TO_CLASS( env_shake, CEnvShake );
  71. BEGIN_DATADESC( CEnvShake )
  72. DEFINE_KEYFIELD( m_Amplitude, FIELD_FLOAT, "amplitude" ),
  73. DEFINE_KEYFIELD( m_Frequency, FIELD_FLOAT, "frequency" ),
  74. DEFINE_KEYFIELD( m_Duration, FIELD_FLOAT, "duration" ),
  75. DEFINE_KEYFIELD( m_Radius, FIELD_FLOAT, "radius" ),
  76. DEFINE_FIELD( m_stopTime, FIELD_TIME ),
  77. DEFINE_FIELD( m_nextShake, FIELD_TIME ),
  78. DEFINE_FIELD( m_currentAmp, FIELD_FLOAT ),
  79. DEFINE_FIELD( m_maxForce, FIELD_VECTOR ),
  80. DEFINE_PHYSPTR( m_pShakeController ),
  81. DEFINE_EMBEDDED( m_shakeCallback ),
  82. DEFINE_INPUTFUNC( FIELD_VOID, "StartShake", InputStartShake ),
  83. DEFINE_INPUTFUNC( FIELD_VOID, "StopShake", InputStopShake ),
  84. DEFINE_INPUTFUNC( FIELD_FLOAT, "Amplitude", InputAmplitude ),
  85. DEFINE_INPUTFUNC( FIELD_FLOAT, "Frequency", InputFrequency ),
  86. END_DATADESC()
  87. #define SF_SHAKE_EVERYONE 0x0001 // Don't check radius
  88. #define SF_SHAKE_INAIR 0x0004 // Shake players in air
  89. #define SF_SHAKE_PHYSICS 0x0008 // Shake physically (not just camera)
  90. #define SF_SHAKE_ROPES 0x0010 // Shake ropes too.
  91. #define SF_SHAKE_NO_VIEW 0x0020 // DON'T shake the view (only ropes and/or physics objects)
  92. #define SF_SHAKE_NO_RUMBLE 0x0040 // DON'T Rumble the XBox Controller
  93. //-----------------------------------------------------------------------------
  94. // Purpose: Destructor.
  95. //-----------------------------------------------------------------------------
  96. CEnvShake::~CEnvShake( void )
  97. {
  98. if ( m_pShakeController )
  99. {
  100. physenv->DestroyMotionController( m_pShakeController );
  101. }
  102. }
  103. float CEnvShake::Radius(bool bPlayers)
  104. {
  105. // The radius for players is zero if SF_SHAKE_EVERYONE is set
  106. if ( bPlayers && HasSpawnFlags(SF_SHAKE_EVERYONE))
  107. return 0;
  108. return m_Radius;
  109. }
  110. //-----------------------------------------------------------------------------
  111. // Purpose: Sets default member values when spawning.
  112. //-----------------------------------------------------------------------------
  113. void CEnvShake::Spawn( void )
  114. {
  115. SetSolid( SOLID_NONE );
  116. SetMoveType( MOVETYPE_NONE );
  117. if ( GetSpawnFlags() & SF_SHAKE_EVERYONE )
  118. {
  119. m_Radius = 0;
  120. }
  121. if ( HasSpawnFlags( SF_SHAKE_NO_VIEW ) && !HasSpawnFlags( SF_SHAKE_PHYSICS ) && !HasSpawnFlags( SF_SHAKE_ROPES ) )
  122. {
  123. DevWarning( "env_shake %s with \"Don't shake view\" spawnflag set without \"Shake physics\" or \"Shake ropes\" spawnflags set.", GetDebugName() );
  124. }
  125. }
  126. //-----------------------------------------------------------------------------
  127. // Purpose: Restore the motion controller
  128. //-----------------------------------------------------------------------------
  129. void CEnvShake::OnRestore( void )
  130. {
  131. BaseClass::OnRestore();
  132. if ( m_pShakeController )
  133. {
  134. m_pShakeController->SetEventHandler( &m_shakeCallback );
  135. }
  136. }
  137. //-----------------------------------------------------------------------------
  138. // Purpose:
  139. //-----------------------------------------------------------------------------
  140. void CEnvShake::ApplyShake( ShakeCommand_t command )
  141. {
  142. if ( !HasSpawnFlags( SF_SHAKE_NO_VIEW ) || !HasSpawnFlags( SF_SHAKE_NO_RUMBLE ) )
  143. {
  144. bool air = (GetSpawnFlags() & SF_SHAKE_INAIR) ? true : false;
  145. UTIL_ScreenShake( GetAbsOrigin(), Amplitude(), Frequency(), Duration(), Radius(), command, air );
  146. }
  147. if ( GetSpawnFlags() & SF_SHAKE_ROPES )
  148. {
  149. CRopeKeyframe::ShakeRopes( GetAbsOrigin(), Radius(false), Frequency() );
  150. }
  151. if ( GetSpawnFlags() & SF_SHAKE_PHYSICS )
  152. {
  153. if ( !m_pShakeController )
  154. {
  155. m_pShakeController = physenv->CreateMotionController( &m_shakeCallback );
  156. }
  157. // do physics shake
  158. switch( command )
  159. {
  160. case SHAKE_START:
  161. case SHAKE_START_NORUMBLE:
  162. case SHAKE_START_RUMBLEONLY:
  163. {
  164. m_stopTime = gpGlobals->curtime + Duration();
  165. m_nextShake = 0;
  166. m_pShakeController->ClearObjects();
  167. SetNextThink( gpGlobals->curtime );
  168. m_currentAmp = Amplitude();
  169. CBaseEntity *list[1024];
  170. float radius = Radius(false);
  171. // probably checked "Shake Everywhere" do a big radius
  172. if ( !radius )
  173. {
  174. radius = 512;
  175. }
  176. Vector extents = Vector(radius, radius, radius);
  177. extents.z = MAX(extents.z, 100);
  178. Vector mins = GetAbsOrigin() - extents;
  179. Vector maxs = GetAbsOrigin() + extents;
  180. int count = UTIL_EntitiesInBox( list, 1024, mins, maxs, 0 );
  181. for ( int i = 0; i < count; i++ )
  182. {
  183. //
  184. // Only shake physics entities that players can see. This is one frame out of date
  185. // so it's possible that we could miss objects if a player changed PVS this frame.
  186. //
  187. if ( ( list[i]->GetMoveType() == MOVETYPE_VPHYSICS ) )
  188. {
  189. IPhysicsObject *pPhys = list[i]->VPhysicsGetObject();
  190. if ( pPhys && pPhys->IsMoveable() )
  191. {
  192. m_pShakeController->AttachObject( pPhys, false );
  193. pPhys->Wake();
  194. }
  195. }
  196. }
  197. }
  198. break;
  199. case SHAKE_STOP:
  200. m_pShakeController->ClearObjects();
  201. break;
  202. case SHAKE_AMPLITUDE:
  203. m_currentAmp = Amplitude();
  204. case SHAKE_FREQUENCY:
  205. m_pShakeController->WakeObjects();
  206. break;
  207. }
  208. }
  209. }
  210. //-----------------------------------------------------------------------------
  211. // Purpose: Input handler that starts the screen shake.
  212. //-----------------------------------------------------------------------------
  213. void CEnvShake::InputStartShake( inputdata_t &inputdata )
  214. {
  215. if ( HasSpawnFlags( SF_SHAKE_NO_RUMBLE ) )
  216. {
  217. ApplyShake( SHAKE_START_NORUMBLE );
  218. }
  219. else if ( HasSpawnFlags( SF_SHAKE_NO_VIEW ) )
  220. {
  221. ApplyShake( SHAKE_START_RUMBLEONLY );
  222. }
  223. else
  224. {
  225. ApplyShake( SHAKE_START );
  226. }
  227. }
  228. //-----------------------------------------------------------------------------
  229. // Purpose: Input handler that stops the screen shake.
  230. //-----------------------------------------------------------------------------
  231. void CEnvShake::InputStopShake( inputdata_t &inputdata )
  232. {
  233. ApplyShake( SHAKE_STOP );
  234. }
  235. //-----------------------------------------------------------------------------
  236. // Purpose: Handles changes to the shake amplitude from an external source.
  237. //-----------------------------------------------------------------------------
  238. void CEnvShake::InputAmplitude( inputdata_t &inputdata )
  239. {
  240. SetAmplitude( inputdata.value.Float() );
  241. ApplyShake( SHAKE_AMPLITUDE );
  242. }
  243. //-----------------------------------------------------------------------------
  244. // Purpose: Handles changes to the shake frequency from an external source.
  245. //-----------------------------------------------------------------------------
  246. void CEnvShake::InputFrequency( inputdata_t &inputdata )
  247. {
  248. SetFrequency( inputdata.value.Float() );
  249. ApplyShake( SHAKE_FREQUENCY );
  250. }
  251. //-----------------------------------------------------------------------------
  252. // Purpose: Calculates the physics shake values
  253. //-----------------------------------------------------------------------------
  254. void CEnvShake::Think( void )
  255. {
  256. int i;
  257. if ( gpGlobals->curtime > m_nextShake )
  258. {
  259. // Higher frequency means we recalc the extents more often and perturb the display again
  260. m_nextShake = gpGlobals->curtime + (1.0f / Frequency());
  261. // Compute random shake extents (the shake will settle down from this)
  262. for (i = 0; i < 2; i++ )
  263. {
  264. m_maxForce[i] = random->RandomFloat( -1, 1 );
  265. }
  266. // make the force it point mostly up
  267. m_maxForce.z = 4;
  268. VectorNormalize( m_maxForce );
  269. m_maxForce *= m_currentAmp * 400; // amplitude is the acceleration of a 100kg object
  270. }
  271. float fraction = ( m_stopTime - gpGlobals->curtime ) / Duration();
  272. if ( fraction < 0 )
  273. {
  274. m_pShakeController->ClearObjects();
  275. return;
  276. }
  277. float freq = 0;
  278. // Ramp up frequency over duration
  279. if ( fraction )
  280. {
  281. freq = (Frequency() / fraction);
  282. }
  283. // square fraction to approach zero more quickly
  284. fraction *= fraction;
  285. // Sine wave that slowly settles to zero
  286. fraction = fraction * sin( gpGlobals->curtime * freq );
  287. // Add to view origin
  288. for ( i = 0; i < 3; i++ )
  289. {
  290. // store the force in the controller callback
  291. m_shakeCallback.m_force[i] = m_maxForce[i] * fraction;
  292. }
  293. // Drop amplitude a bit, less for higher frequency shakes
  294. m_currentAmp -= m_currentAmp * ( gpGlobals->frametime / (Duration() * Frequency()) );
  295. SetNextThink( gpGlobals->curtime );
  296. }
  297. //------------------------------------------------------------------------------
  298. // Purpose: Console command to cause a screen shake.
  299. //------------------------------------------------------------------------------
  300. void CC_Shake( void )
  301. {
  302. CBasePlayer *pPlayer = UTIL_GetCommandClient();
  303. if (pPlayer)
  304. {
  305. UTIL_ScreenShake( pPlayer->WorldSpaceCenter(), 25.0, 150.0, 1.0, 750, SHAKE_START );
  306. }
  307. }
  308. //-----------------------------------------------------------------------------
  309. // Purpose: Draw any debug text overlays
  310. // Returns current text offset from the top
  311. //-----------------------------------------------------------------------------
  312. int CEnvShake::DrawDebugTextOverlays( void )
  313. {
  314. int text_offset = BaseClass::DrawDebugTextOverlays();
  315. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  316. {
  317. char tempstr[512];
  318. // print amplitude
  319. Q_snprintf(tempstr,sizeof(tempstr)," magnitude: %f", m_Amplitude);
  320. EntityText(text_offset,tempstr,0);
  321. text_offset++;
  322. // print frequency
  323. Q_snprintf(tempstr,sizeof(tempstr)," frequency: %f", m_Frequency);
  324. EntityText(text_offset,tempstr,0);
  325. text_offset++;
  326. // print duration
  327. Q_snprintf(tempstr,sizeof(tempstr)," duration: %f", m_Duration);
  328. EntityText(text_offset,tempstr,0);
  329. text_offset++;
  330. // print radius
  331. Q_snprintf(tempstr,sizeof(tempstr)," radius: %f", m_Radius);
  332. EntityText(text_offset,tempstr,0);
  333. text_offset++;
  334. }
  335. return text_offset;
  336. }
  337. static ConCommand shake("shake", CC_Shake, "Shake the screen.", FCVAR_CHEAT );