|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements a screen shake effect that can also shake physics objects.
//
// NOTE: UTIL_ScreenShake() will only shake players who are on the ground
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "shake.h"
#include "physics_saverestore.h"
#include "rope.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
class CPhysicsShake : public IMotionEvent { DECLARE_SIMPLE_DATADESC();
public: virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) { Vector contact; if ( !pObject->GetContactPoint( &contact, NULL ) ) return SIM_NOTHING;
// fudge the force a bit to make it more dramatic
pObject->CalculateForceOffset( m_force * (1.0f + pObject->GetMass()*0.4f), contact, &linear, &angular );
return SIM_LOCAL_FORCE; }
Vector m_force; };
BEGIN_SIMPLE_DATADESC( CPhysicsShake ) DEFINE_FIELD( m_force, FIELD_VECTOR ), END_DATADESC()
class CEnvShake : public CPointEntity { private: float m_Amplitude; float m_Frequency; float m_Duration; float m_Radius; // radius of 0 means all players
float m_stopTime; float m_nextShake; float m_currentAmp;
Vector m_maxForce;
IPhysicsMotionController *m_pShakeController; CPhysicsShake m_shakeCallback;
DECLARE_DATADESC();
public: DECLARE_CLASS( CEnvShake, CPointEntity );
~CEnvShake( void ); virtual void Spawn( void ); virtual void OnRestore( void );
inline float Amplitude( void ) { return m_Amplitude; } inline float Frequency( void ) { return m_Frequency; } inline float Duration( void ) { return m_Duration; } float Radius( bool bPlayers = true ); inline void SetAmplitude( float amplitude ) { m_Amplitude = amplitude; } inline void SetFrequency( float frequency ) { m_Frequency = frequency; } inline void SetDuration( float duration ) { m_Duration = duration; } inline void SetRadius( float radius ) { m_Radius = radius; }
int DrawDebugTextOverlays(void);
// Input handlers
void InputStartShake( inputdata_t &inputdata ); void InputStopShake( inputdata_t &inputdata ); void InputAmplitude( inputdata_t &inputdata ); void InputFrequency( inputdata_t &inputdata );
// Causes the camera/physics shakes to happen:
void ApplyShake( ShakeCommand_t command ); void Think( void ); };
LINK_ENTITY_TO_CLASS( env_shake, CEnvShake );
BEGIN_DATADESC( CEnvShake )
DEFINE_KEYFIELD( m_Amplitude, FIELD_FLOAT, "amplitude" ), DEFINE_KEYFIELD( m_Frequency, FIELD_FLOAT, "frequency" ), DEFINE_KEYFIELD( m_Duration, FIELD_FLOAT, "duration" ), DEFINE_KEYFIELD( m_Radius, FIELD_FLOAT, "radius" ), DEFINE_FIELD( m_stopTime, FIELD_TIME ), DEFINE_FIELD( m_nextShake, FIELD_TIME ), DEFINE_FIELD( m_currentAmp, FIELD_FLOAT ), DEFINE_FIELD( m_maxForce, FIELD_VECTOR ), DEFINE_PHYSPTR( m_pShakeController ), DEFINE_EMBEDDED( m_shakeCallback ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartShake", InputStartShake ), DEFINE_INPUTFUNC( FIELD_VOID, "StopShake", InputStopShake ), DEFINE_INPUTFUNC( FIELD_FLOAT, "Amplitude", InputAmplitude ), DEFINE_INPUTFUNC( FIELD_FLOAT, "Frequency", InputFrequency ),
END_DATADESC()
#define SF_SHAKE_EVERYONE 0x0001 // Don't check radius
#define SF_SHAKE_INAIR 0x0004 // Shake players in air
#define SF_SHAKE_PHYSICS 0x0008 // Shake physically (not just camera)
#define SF_SHAKE_ROPES 0x0010 // Shake ropes too.
#define SF_SHAKE_NO_VIEW 0x0020 // DON'T shake the view (only ropes and/or physics objects)
#define SF_SHAKE_NO_RUMBLE 0x0040 // DON'T Rumble the XBox Controller
//-----------------------------------------------------------------------------
// Purpose: Destructor.
//-----------------------------------------------------------------------------
CEnvShake::~CEnvShake( void ) { if ( m_pShakeController ) { physenv->DestroyMotionController( m_pShakeController ); } }
float CEnvShake::Radius(bool bPlayers) { // The radius for players is zero if SF_SHAKE_EVERYONE is set
if ( bPlayers && HasSpawnFlags(SF_SHAKE_EVERYONE)) return 0; return m_Radius; }
//-----------------------------------------------------------------------------
// Purpose: Sets default member values when spawning.
//-----------------------------------------------------------------------------
void CEnvShake::Spawn( void ) { SetSolid( SOLID_NONE ); SetMoveType( MOVETYPE_NONE ); if ( GetSpawnFlags() & SF_SHAKE_EVERYONE ) { m_Radius = 0; } if ( HasSpawnFlags( SF_SHAKE_NO_VIEW ) && !HasSpawnFlags( SF_SHAKE_PHYSICS ) && !HasSpawnFlags( SF_SHAKE_ROPES ) ) { DevWarning( "env_shake %s with \"Don't shake view\" spawnflag set without \"Shake physics\" or \"Shake ropes\" spawnflags set.", GetDebugName() ); } }
//-----------------------------------------------------------------------------
// Purpose: Restore the motion controller
//-----------------------------------------------------------------------------
void CEnvShake::OnRestore( void ) { BaseClass::OnRestore();
if ( m_pShakeController ) { m_pShakeController->SetEventHandler( &m_shakeCallback ); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvShake::ApplyShake( ShakeCommand_t command ) { if ( !HasSpawnFlags( SF_SHAKE_NO_VIEW ) || !HasSpawnFlags( SF_SHAKE_NO_RUMBLE ) ) { bool air = (GetSpawnFlags() & SF_SHAKE_INAIR) ? true : false; UTIL_ScreenShake( GetAbsOrigin(), Amplitude(), Frequency(), Duration(), Radius(), command, air ); } if ( GetSpawnFlags() & SF_SHAKE_ROPES ) { CRopeKeyframe::ShakeRopes( GetAbsOrigin(), Radius(false), Frequency() ); }
if ( GetSpawnFlags() & SF_SHAKE_PHYSICS ) { if ( !m_pShakeController ) { m_pShakeController = physenv->CreateMotionController( &m_shakeCallback ); } // do physics shake
switch( command ) { case SHAKE_START: case SHAKE_START_NORUMBLE: case SHAKE_START_RUMBLEONLY: { m_stopTime = gpGlobals->curtime + Duration(); m_nextShake = 0; m_pShakeController->ClearObjects(); SetNextThink( gpGlobals->curtime ); m_currentAmp = Amplitude(); CBaseEntity *list[1024]; float radius = Radius(false); // probably checked "Shake Everywhere" do a big radius
if ( !radius ) { radius = 512; } Vector extents = Vector(radius, radius, radius); extents.z = MAX(extents.z, 100); Vector mins = GetAbsOrigin() - extents; Vector maxs = GetAbsOrigin() + extents; int count = UTIL_EntitiesInBox( list, 1024, mins, maxs, 0 );
for ( int i = 0; i < count; i++ ) { //
// Only shake physics entities that players can see. This is one frame out of date
// so it's possible that we could miss objects if a player changed PVS this frame.
//
if ( ( list[i]->GetMoveType() == MOVETYPE_VPHYSICS ) ) { IPhysicsObject *pPhys = list[i]->VPhysicsGetObject(); if ( pPhys && pPhys->IsMoveable() ) { m_pShakeController->AttachObject( pPhys, false ); pPhys->Wake(); } } } } break; case SHAKE_STOP: m_pShakeController->ClearObjects(); break; case SHAKE_AMPLITUDE: m_currentAmp = Amplitude(); case SHAKE_FREQUENCY: m_pShakeController->WakeObjects(); break; } } }
//-----------------------------------------------------------------------------
// Purpose: Input handler that starts the screen shake.
//-----------------------------------------------------------------------------
void CEnvShake::InputStartShake( inputdata_t &inputdata ) { if ( HasSpawnFlags( SF_SHAKE_NO_RUMBLE ) ) { ApplyShake( SHAKE_START_NORUMBLE ); } else if ( HasSpawnFlags( SF_SHAKE_NO_VIEW ) ) { ApplyShake( SHAKE_START_RUMBLEONLY ); } else { ApplyShake( SHAKE_START ); } }
//-----------------------------------------------------------------------------
// Purpose: Input handler that stops the screen shake.
//-----------------------------------------------------------------------------
void CEnvShake::InputStopShake( inputdata_t &inputdata ) { ApplyShake( SHAKE_STOP ); }
//-----------------------------------------------------------------------------
// Purpose: Handles changes to the shake amplitude from an external source.
//-----------------------------------------------------------------------------
void CEnvShake::InputAmplitude( inputdata_t &inputdata ) { SetAmplitude( inputdata.value.Float() ); ApplyShake( SHAKE_AMPLITUDE ); }
//-----------------------------------------------------------------------------
// Purpose: Handles changes to the shake frequency from an external source.
//-----------------------------------------------------------------------------
void CEnvShake::InputFrequency( inputdata_t &inputdata ) { SetFrequency( inputdata.value.Float() ); ApplyShake( SHAKE_FREQUENCY ); }
//-----------------------------------------------------------------------------
// Purpose: Calculates the physics shake values
//-----------------------------------------------------------------------------
void CEnvShake::Think( void ) { int i;
if ( gpGlobals->curtime > m_nextShake ) { // Higher frequency means we recalc the extents more often and perturb the display again
m_nextShake = gpGlobals->curtime + (1.0f / Frequency());
// Compute random shake extents (the shake will settle down from this)
for (i = 0; i < 2; i++ ) { m_maxForce[i] = random->RandomFloat( -1, 1 ); } // make the force it point mostly up
m_maxForce.z = 4; VectorNormalize( m_maxForce ); m_maxForce *= m_currentAmp * 400; // amplitude is the acceleration of a 100kg object
}
float fraction = ( m_stopTime - gpGlobals->curtime ) / Duration();
if ( fraction < 0 ) { m_pShakeController->ClearObjects(); return; }
float freq = 0; // Ramp up frequency over duration
if ( fraction ) { freq = (Frequency() / fraction); }
// square fraction to approach zero more quickly
fraction *= fraction;
// Sine wave that slowly settles to zero
fraction = fraction * sin( gpGlobals->curtime * freq );
// Add to view origin
for ( i = 0; i < 3; i++ ) { // store the force in the controller callback
m_shakeCallback.m_force[i] = m_maxForce[i] * fraction; }
// Drop amplitude a bit, less for higher frequency shakes
m_currentAmp -= m_currentAmp * ( gpGlobals->frametime / (Duration() * Frequency()) ); SetNextThink( gpGlobals->curtime ); }
//------------------------------------------------------------------------------
// Purpose: Console command to cause a screen shake.
//------------------------------------------------------------------------------
void CC_Shake( void ) { CBasePlayer *pPlayer = UTIL_GetCommandClient(); if (pPlayer) { UTIL_ScreenShake( pPlayer->WorldSpaceCenter(), 25.0, 150.0, 1.0, 750, SHAKE_START ); } }
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Returns current text offset from the top
//-----------------------------------------------------------------------------
int CEnvShake::DrawDebugTextOverlays( void ) { int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT) { char tempstr[512];
// print amplitude
Q_snprintf(tempstr,sizeof(tempstr)," magnitude: %f", m_Amplitude); EntityText(text_offset,tempstr,0); text_offset++;
// print frequency
Q_snprintf(tempstr,sizeof(tempstr)," frequency: %f", m_Frequency); EntityText(text_offset,tempstr,0); text_offset++;
// print duration
Q_snprintf(tempstr,sizeof(tempstr)," duration: %f", m_Duration); EntityText(text_offset,tempstr,0); text_offset++;
// print radius
Q_snprintf(tempstr,sizeof(tempstr)," radius: %f", m_Radius); EntityText(text_offset,tempstr,0); text_offset++;
} return text_offset; }
static ConCommand shake("shake", CC_Shake, "Shake the screen.", FCVAR_CHEAT );
|