aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/client/c_rumble.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/client/c_rumble.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/client/c_rumble.cpp')
-rw-r--r--mp/src/game/client/c_rumble.cpp826
1 files changed, 826 insertions, 0 deletions
diff --git a/mp/src/game/client/c_rumble.cpp b/mp/src/game/client/c_rumble.cpp
new file mode 100644
index 00000000..a54858b6
--- /dev/null
+++ b/mp/src/game/client/c_rumble.cpp
@@ -0,0 +1,826 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Rumble effects mixer for XBox
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "c_rumble.h"
+#include "rumble_shared.h"
+#include "inputsystem/iinputsystem.h"
+
+ConVar cl_rumblescale( "cl_rumblescale", "1.0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Scale sensitivity of rumble effects (0 to 1.0)" );
+ConVar cl_debugrumble( "cl_debugrumble", "0", FCVAR_ARCHIVE, "Turn on rumble debugging spew" );
+
+#define MAX_RUMBLE_CHANNELS 3 // Max concurrent rumble effects
+
+#define NUM_WAVE_SAMPLES 30 // Effects play at 10hz
+
+typedef struct
+{
+ float amplitude_left[NUM_WAVE_SAMPLES];
+ float amplitude_right[NUM_WAVE_SAMPLES];
+ int numSamples;
+} RumbleWaveform_t;
+
+//=========================================================
+// Structure for a rumble effect channel. This is akin to
+// a sound channel that is playing a sound.
+//=========================================================
+typedef struct
+{
+ float starttime; // When did this effect start playing? (gpGlobals->curtime)
+ int waveformIndex; // Type of effect waveform used (an enum from rumble_shared.h)
+ int priority; // How important this effect is (for making replacement decisions)
+ bool in_use; // Is this channel in use?? (true if effect is currently playing, false if done or otherwise available)
+ unsigned char rumbleFlags; // Flags pertaining to the effect currently playing on this channel.
+ float scale; // Some effects are updated while they are running.
+} RumbleChannel_t;
+
+//=========================================================
+// This structure contains parameters necessary to generate
+// a sine or sawtooth waveform.
+//=========================================================
+typedef struct tagWaveGenParams
+{
+ float cycles; // AKA frequency
+ float amplitudescale;
+ bool leftChannel; // If false, generating for the right channel
+
+ float maxAmplitude; // Clamping
+ float minAmplitude;
+
+ void Set( float c_cycles, float c_amplitudescale, bool c_leftChannel, float c_minAmplitude, float c_maxAmplitude )
+ {
+ cycles = c_cycles;
+ amplitudescale = c_amplitudescale;
+ leftChannel = c_leftChannel;
+ minAmplitude = c_minAmplitude;
+ maxAmplitude = c_maxAmplitude;
+ }
+
+ // CTOR
+ tagWaveGenParams( float c_cycles, float c_amplitudescale, bool c_leftChannel, float c_minAmplitude, float c_maxAmplitude )
+ {
+ Set( c_cycles, c_amplitudescale, c_leftChannel, c_minAmplitude, c_maxAmplitude );
+ }
+
+} WaveGenParams_t;
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void TerminateWaveform( RumbleWaveform_t *pWaveform, int samples )
+{
+ if( samples <= NUM_WAVE_SAMPLES )
+ {
+ pWaveform->numSamples = samples;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void EaseInWaveform( RumbleWaveform_t *pWaveform, int samples, bool left )
+{
+ float step = 1.0f / ((float)samples);
+ float factor = 0.0f;
+
+ for( int i = 0 ; i < samples ; i++ )
+ {
+ if( left )
+ {
+ pWaveform->amplitude_left[i] *= factor;
+ }
+ else
+ {
+ pWaveform->amplitude_right[i] *= factor;
+ }
+
+ factor += step;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void EaseOutWaveform( RumbleWaveform_t *pWaveform, int samples, bool left )
+{
+ float step = 1.0f / ((float)samples);
+ float factor = 0.0f;
+
+ int i = NUM_WAVE_SAMPLES - 1;
+
+ for( int j = 0 ; j < samples ; j++ )
+ {
+ if( left )
+ {
+ pWaveform->amplitude_left[i] *= factor;
+ }
+ else
+ {
+ pWaveform->amplitude_right[i] *= factor;
+ }
+
+ factor += step;
+ i--;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void GenerateSawtoothEffect( RumbleWaveform_t *pWaveform, const WaveGenParams_t &params )
+{
+ float delta = params.maxAmplitude - params.minAmplitude;
+ int waveLength = NUM_WAVE_SAMPLES / params.cycles;
+ float vstep = (delta / waveLength);
+
+ float amplitude = params.minAmplitude;
+
+ for( int i = 0 ; i < NUM_WAVE_SAMPLES ; i++ )
+ {
+ if( params.leftChannel )
+ {
+ pWaveform->amplitude_left[i] = amplitude;
+ }
+ else
+ {
+ pWaveform->amplitude_right[i] = amplitude;
+ }
+
+ amplitude += vstep;
+
+ if( amplitude > params.maxAmplitude )
+ {
+ amplitude = params.minAmplitude;
+ }
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void GenerateSquareWaveEffect( RumbleWaveform_t *pWaveform, const WaveGenParams_t &params )
+{
+ int i = 0;
+ int j;
+
+ int steps = ((float)NUM_WAVE_SAMPLES) / (params.cycles*2.0f);
+
+ while( i < NUM_WAVE_SAMPLES )
+ {
+ for( j = 0 ; j < steps ; j++ )
+ {
+ if( params.leftChannel )
+ {
+ pWaveform->amplitude_left[i++] = params.minAmplitude;
+ }
+ else
+ {
+ pWaveform->amplitude_right[i++] = params.minAmplitude;
+ }
+ }
+ for( j = 0 ; j < steps ; j++ )
+ {
+ if( params.leftChannel )
+ {
+ pWaveform->amplitude_left[i++] = params.maxAmplitude;
+ }
+ else
+ {
+ pWaveform->amplitude_right[i++] = params.maxAmplitude;
+ }
+ }
+ }
+}
+
+//---------------------------------------------------------
+// If you pass a numSamples, this wave will only be that many
+// samples long.
+//---------------------------------------------------------
+void GenerateFlatEffect( RumbleWaveform_t *pWaveform, const WaveGenParams_t &params )
+{
+ for( int i = 0 ; i < NUM_WAVE_SAMPLES ; i++ )
+ {
+ if( params.leftChannel )
+ {
+ pWaveform->amplitude_left[i] = params.maxAmplitude;
+ }
+ else
+ {
+ pWaveform->amplitude_right[i] = params.maxAmplitude;
+ }
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void GenerateSineWaveEffect( RumbleWaveform_t *pWaveform, const WaveGenParams_t &params )
+{
+ float step = (360.0f * (params.cycles * 0.5f) ) / ((float)NUM_WAVE_SAMPLES);
+ float degrees = 180.0f + step; // 180 to start at 0
+
+ for( int i = 0 ; i < NUM_WAVE_SAMPLES ; i++ )
+ {
+ float radians = DEG2RAD(degrees);
+ float value = fabs( sin(radians) );
+
+ value *= params.amplitudescale;
+
+ if( value < params.minAmplitude )
+ value = params.minAmplitude;
+
+ if( value > params.maxAmplitude )
+ value = params.maxAmplitude;
+
+ if( params.leftChannel )
+ {
+ pWaveform->amplitude_left[i] = value;
+ }
+ else
+ {
+ pWaveform->amplitude_right[i] = value;
+ }
+
+ degrees += step;
+ }
+}
+
+//=========================================================
+//=========================================================
+class CRumbleEffects
+{
+public:
+ CRumbleEffects()
+ {
+ Init();
+ }
+
+ void Init();
+ void SetOutputEnabled( bool bEnable );
+ void StartEffect( unsigned char effectIndex, unsigned char rumbleData, unsigned char rumbleFlags );
+ void StopEffect( int effectIndex );
+ void StopAllEffects();
+ void ComputeAmplitudes( RumbleChannel_t *pChannel, float curtime, float *pLeft, float *pRight );
+ void UpdateEffects( float curtime );
+ void UpdateScreenShakeRumble( float shake, float balance );
+
+ RumbleChannel_t *FindExistingChannel( int index );
+ RumbleChannel_t *FindAvailableChannel( int priority );
+
+public:
+ RumbleChannel_t m_Channels[ MAX_RUMBLE_CHANNELS ];
+
+ RumbleWaveform_t m_Waveforms[ NUM_RUMBLE_EFFECTS ];
+
+ float m_flScreenShake;
+ bool m_bOutputEnabled;
+};
+
+
+CRumbleEffects g_RumbleEffects;
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CRumbleEffects::Init()
+{
+ SetOutputEnabled( true );
+
+ int i;
+
+ for( i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ )
+ {
+ m_Channels[i].in_use = false;
+ m_Channels[i].priority = 0;
+ }
+
+ // Every effect defaults to this many samples. Call TerminateWaveform() to trim these.
+ for ( i = 0 ; i < NUM_RUMBLE_EFFECTS ; i++ )
+ {
+ m_Waveforms[i].numSamples = NUM_WAVE_SAMPLES;
+ }
+
+ // Jeep Idle
+ WaveGenParams_t params( 1, 1.0f, false, 0.0f, 0.15f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_JEEP_ENGINE_LOOP], params );
+
+ // Pistol
+ params.Set( 1, 1.0f, false, 0.0f, 0.5f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_PISTOL], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_PISTOL], 3 );
+
+ // SMG1
+ params.Set( 1, 1.0f, true, 0.0f, 0.2f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_SMG1], params );
+ params.Set( 1, 1.0f, false, 0.0f, 0.4f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_SMG1], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_SMG1], 3 );
+
+ // AR2
+ params.Set( 1, 1.0f, true, 0.0f, 0.5f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_AR2], params );
+ params.Set( 1, 1.0f, false, 0.0f, 0.3f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_AR2], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_AR2], 3 );
+
+ // AR2 Alt
+ params.Set( 1, 1.0f, true, 0.0, 0.5f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_AR2_ALT_FIRE], params );
+ EaseInWaveform( &m_Waveforms[RUMBLE_AR2_ALT_FIRE], 5, true );
+ params.Set( 1, 1.0f, false, 0.0, 0.7f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_AR2_ALT_FIRE], params );
+ EaseInWaveform( &m_Waveforms[RUMBLE_AR2_ALT_FIRE], 5, false );
+ TerminateWaveform( &m_Waveforms[RUMBLE_AR2_ALT_FIRE], 7 );
+
+ // 357
+ params.Set( 1, 1.0f, true, 0.0f, 0.75f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_357], params );
+ params.Set( 1, 1.0f, false, 0.0f, 0.75f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_357], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_357], 2 );
+
+ // Shotgun
+ params.Set( 1, 1.0f, true, 0.0f, 0.8f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_SHOTGUN_SINGLE], params );
+ params.Set( 1, 1.0f, false, 0.0f, 0.8f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_SHOTGUN_SINGLE], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_SHOTGUN_SINGLE], 3 );
+
+ params.Set( 1, 1.0f, true, 0.0f, 1.0f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_SHOTGUN_DOUBLE], params );
+ params.Set( 1, 1.0f, false, 0.0f, 1.0f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_SHOTGUN_DOUBLE], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_SHOTGUN_DOUBLE], 3 );
+
+ // RPG Missile
+ params.Set( 1, 1.0f, false, 0.0f, 0.3f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_RPG_MISSILE], params );
+ EaseOutWaveform( &m_Waveforms[RUMBLE_RPG_MISSILE], 30, false );
+ TerminateWaveform( &m_Waveforms[RUMBLE_RPG_MISSILE], 6 );
+
+ // Physcannon open forks
+ params.Set( 1, 1.0f, false, 0.0f, 0.25f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_PHYSCANNON_OPEN], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_PHYSCANNON_OPEN], 4 );
+
+ // Physcannon holding something
+ params.Set( 1, 1.0f, true, 0.0f, 0.2f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_PHYSCANNON_LOW], params );
+ params.Set( 6, 1.0f, false, 0.0f, 0.25f );
+ GenerateSquareWaveEffect( &m_Waveforms[RUMBLE_PHYSCANNON_LOW], params );
+
+ // Crowbar
+ params.Set( 1, 1.0f, false, 0.0f, 0.35f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_CROWBAR_SWING], params );
+ EaseOutWaveform( &m_Waveforms[RUMBLE_CROWBAR_SWING], 30, false );
+ TerminateWaveform( &m_Waveforms[RUMBLE_CROWBAR_SWING], 4 );
+
+ // Airboat gun
+ params.Set( 1, 1.0f, false, 0.0f, 0.4f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_AIRBOAT_GUN], params );
+ params.Set( 12, 1.0f, true, 0.0f, 0.5f );
+ GenerateSawtoothEffect( &m_Waveforms[RUMBLE_AIRBOAT_GUN], params );
+
+ // Generic flat effects.
+ params.Set( 1, 1.0f, true, 0.0f, 1.0f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_FLAT_LEFT], params );
+
+ params.Set( 1, 1.0f, false, 0.0f, 1.0f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_FLAT_RIGHT], params );
+
+ params.Set( 1, 1.0f, true, 0.0f, 1.0f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_FLAT_BOTH], params );
+ params.Set( 1, 1.0f, false, 0.0f, 1.0f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_FLAT_BOTH], params );
+
+ // Impact from a long fall
+ params.Set( 1, 1.0f, false, 0.0f, 0.5f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_FALL_LONG], params );
+ params.Set( 1, 1.0f, true, 0.0f, 0.5f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_FALL_LONG], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_FALL_LONG], 3 );
+
+ // Impact from a short fall
+ params.Set( 1, 1.0f, false, 0.0f, 0.3f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_FALL_SHORT], params );
+ params.Set( 1, 1.0f, true, 0.0f, 0.3f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_FALL_SHORT], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_FALL_SHORT], 2 );
+
+ // Portalgun left (blue) shot
+ params.Set( 1, 1.0f, true, 0.0f, 0.3f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_PORTALGUN_LEFT], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_PORTALGUN_LEFT], 2 );
+
+ // Portalgun right (red) shot
+ params.Set( 1, 1.0f, false, 0.0f, 0.3f );
+ GenerateFlatEffect( &m_Waveforms[RUMBLE_PORTALGUN_RIGHT], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_PORTALGUN_RIGHT], 2 );
+
+ // Portal failed to place feedback
+ params.Set( 12, 1.0f, true, 0.0f, 1.0f );
+ GenerateSquareWaveEffect( &m_Waveforms[RUMBLE_PORTAL_PLACEMENT_FAILURE], params );
+ params.Set( 12, 1.0f, false, 0.0f, 1.0f );
+ GenerateSquareWaveEffect( &m_Waveforms[RUMBLE_PORTAL_PLACEMENT_FAILURE], params );
+ TerminateWaveform( &m_Waveforms[RUMBLE_PORTAL_PLACEMENT_FAILURE], 6 );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+RumbleChannel_t *CRumbleEffects::FindExistingChannel( int index )
+{
+ RumbleChannel_t *pChannel;
+
+ for( int i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ )
+ {
+ pChannel = &m_Channels[i];
+
+ if( pChannel->in_use && pChannel->waveformIndex == index )
+ {
+ // This effect is already playing. Provide this channel for the
+ // effect to be re-started on.
+ return pChannel;
+ }
+ }
+
+ return NULL;
+}
+
+//---------------------------------------------------------
+// priority - the priority of the effect we want to play.
+//---------------------------------------------------------
+RumbleChannel_t *CRumbleEffects::FindAvailableChannel( int priority )
+{
+ RumbleChannel_t *pChannel;
+ int i;
+
+ for( i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ )
+ {
+ pChannel = &m_Channels[i];
+
+ if( !pChannel->in_use )
+ {
+ return pChannel;
+ }
+ }
+
+ int lowestPriority = priority;
+ RumbleChannel_t *pBestChannel = NULL;
+ float oldestChannel = FLT_MAX;
+
+ // All channels already in use. Find a channel to slam.
+ for( i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ )
+ {
+ pChannel = &m_Channels[i];
+
+ if( (pChannel->rumbleFlags & RUMBLE_FLAG_LOOP) )
+ continue;
+
+ if( pChannel->priority < lowestPriority )
+ {
+ // Always happily slam a lower priority sound.
+ pBestChannel = pChannel;
+ lowestPriority = pChannel->priority;
+ }
+ else if( pChannel->priority == lowestPriority )
+ {
+ // Priority is the same, so replace the oldest.
+ if( pBestChannel )
+ {
+ // If we already have a channel of the same priority to discard, make sure we discard the oldest.
+ float age = gpGlobals->curtime - pChannel->starttime;
+
+ if( age > oldestChannel )
+ {
+ pBestChannel = pChannel;
+ oldestChannel = age;
+ }
+ }
+ else
+ {
+ // Take it.
+ pBestChannel = pChannel;
+ oldestChannel = gpGlobals->curtime - pChannel->starttime;
+ }
+ }
+ }
+
+ return pBestChannel; // Can still be NULL if we couldn't find a channel to slam.
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CRumbleEffects::SetOutputEnabled( bool bEnable )
+{
+ m_bOutputEnabled = bEnable;
+
+ if( !bEnable )
+ {
+ // Tell the hardware to shut down motors right now, in case this gets called
+ // and some other process blocks us before the next rumble system update.
+ m_flScreenShake = 0.0f;
+
+ inputsystem->StopRumble();
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CRumbleEffects::StartEffect( unsigned char effectIndex, unsigned char rumbleData, unsigned char rumbleFlags )
+{
+ if( effectIndex == RUMBLE_STOP_ALL )
+ {
+ StopAllEffects();
+ return;
+ }
+
+ if( rumbleFlags & RUMBLE_FLAG_STOP )
+ {
+ StopEffect( effectIndex );
+ return;
+ }
+
+ int priority = 1;
+ RumbleChannel_t *pChannel = NULL;
+
+ if( (rumbleFlags & RUMBLE_FLAG_RESTART) )
+ {
+ // Try to find any active instance of this effect and replace it.
+ pChannel = FindExistingChannel( effectIndex );
+ }
+
+ if( (rumbleFlags & RUMBLE_FLAG_ONLYONE) )
+ {
+ pChannel = FindExistingChannel( effectIndex );
+
+ if( pChannel )
+ {
+ // Bail out. An instance of this effect is already playing.
+ return;
+ }
+ }
+
+ if( (rumbleFlags & RUMBLE_FLAG_UPDATE_SCALE) )
+ {
+ pChannel = FindExistingChannel( effectIndex );
+ if( pChannel )
+ {
+ pChannel->scale = ((float)rumbleData) / 100.0f;
+ }
+
+ // It's possible to return without finding a rumble to update.
+ // This means you tried to update a rumble you never started.
+ return;
+ }
+
+ if( !pChannel )
+ {
+ pChannel = FindAvailableChannel( priority );
+ }
+
+ if( pChannel )
+ {
+ pChannel->waveformIndex = effectIndex;
+ pChannel->priority = 1;
+ pChannel->starttime = gpGlobals->curtime;
+ pChannel->in_use = true;
+ pChannel->rumbleFlags = rumbleFlags;
+
+ if( rumbleFlags & RUMBLE_FLAG_INITIAL_SCALE )
+ {
+ pChannel->scale = ((float)rumbleData) / 100.0f;
+ }
+ else
+ {
+ pChannel->scale = 1.0f;
+ }
+ }
+
+ if( (rumbleFlags & RUMBLE_FLAG_RANDOM_AMPLITUDE) )
+ {
+ pChannel->scale = random->RandomFloat( 0.1f, 1.0f );
+ }
+}
+
+//---------------------------------------------------------
+// Find all playing effects of this type and stop them.
+//---------------------------------------------------------
+void CRumbleEffects::StopEffect( int effectIndex )
+{
+ for( int i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ )
+ {
+ if( m_Channels[i].in_use && m_Channels[i].waveformIndex == effectIndex )
+ {
+ m_Channels[i].in_use = false;
+ }
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CRumbleEffects::StopAllEffects()
+{
+ for( int i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ )
+ {
+ m_Channels[i].in_use = false;
+ }
+
+ m_flScreenShake = 0.0f;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CRumbleEffects::ComputeAmplitudes( RumbleChannel_t *pChannel, float curtime, float *pLeft, float *pRight )
+{
+ // How long has this waveform been playing?
+ float elapsed = curtime - pChannel->starttime;
+
+ if( elapsed >= (NUM_WAVE_SAMPLES/10) )
+ {
+ if( (pChannel->rumbleFlags & RUMBLE_FLAG_LOOP) )
+ {
+ // This effect loops. Just fixup the start time and recompute elapsed.
+ pChannel->starttime = curtime;
+ elapsed = curtime - pChannel->starttime;
+ }
+ else
+ {
+ // This effect is done! Should it loop?
+ *pLeft = 0;
+ *pRight = 0;
+ pChannel->in_use = false;
+ return;
+ }
+ }
+
+ // Figure out which sample we're playing FROM.
+ int seconds = ((int) elapsed);
+ int sample = (int)(elapsed*10.0f);
+
+ // Get the fraction bit.
+ float fraction = elapsed - seconds;
+
+ float left, right;
+
+ if( sample == m_Waveforms[pChannel->waveformIndex].numSamples )
+ {
+ // This effect is done. Send zeroes to the mixer for this
+ // final frame and then turn the channel off. (Unless it loops!)
+
+ if( (pChannel->rumbleFlags & RUMBLE_FLAG_LOOP) )
+ {
+ // Loop this effect
+ pChannel->starttime = gpGlobals->curtime;
+
+ // Send the first sample.
+ left = m_Waveforms[pChannel->waveformIndex].amplitude_left[0];
+ right = m_Waveforms[pChannel->waveformIndex].amplitude_right[0];
+ }
+ else
+ {
+ left = 0.0f;
+ right = 0.0f;
+ pChannel->in_use = false;
+ }
+ }
+ else
+ {
+ // Use values for the last sample that we have passed
+ left = m_Waveforms[pChannel->waveformIndex].amplitude_left[sample];
+ right = m_Waveforms[pChannel->waveformIndex].amplitude_right[sample];
+ }
+
+ left *= pChannel->scale;
+ right *= pChannel->scale;
+
+ if( cl_debugrumble.GetBool() )
+ {
+ Msg("Seconds:%d Fraction:%f Sample:%d L:%f R:%f\n", seconds, fraction, sample, left, right );
+ }
+
+ if( !m_bOutputEnabled )
+ {
+ // Send zeroes to stop any current rumbling, and to keep it silenced.
+ left = 0;
+ right = 0;
+ }
+
+ *pLeft = left;
+ *pRight = right;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CRumbleEffects::UpdateScreenShakeRumble( float shake, float balance )
+{
+ if( m_bOutputEnabled )
+ {
+ m_flScreenShake = shake;
+ }
+ else
+ {
+ // Silence
+ m_flScreenShake = 0.0f;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CRumbleEffects::UpdateEffects( float curtime )
+{
+ float fLeftMotor = 0.0f;
+ float fRightMotor = 0.0f;
+
+ for( int i = 0 ; i < MAX_RUMBLE_CHANNELS ; i++ )
+ {
+ // Expire old channels
+ RumbleChannel_t *pChannel = & m_Channels[i];
+
+ if( pChannel->in_use )
+ {
+ float left, right;
+
+ ComputeAmplitudes( pChannel, curtime, &left, &right );
+
+ fLeftMotor += left;
+ fRightMotor += right;
+ }
+ }
+
+ // Add in any screenshake
+ float shakeLeft = 0.0f;
+ float shakeRight = 0.0f;
+ if( m_flScreenShake != 0.0f )
+ {
+ if( m_flScreenShake < 0.0f )
+ {
+ shakeLeft = fabs( m_flScreenShake );
+ }
+ else
+ {
+ shakeRight = m_flScreenShake;
+ }
+ }
+
+ fLeftMotor += shakeLeft;
+ fRightMotor += shakeRight;
+
+ fLeftMotor *= cl_rumblescale.GetFloat();
+ fRightMotor *= cl_rumblescale.GetFloat();
+
+ if( engine->IsPaused() )
+ {
+ // Send nothing when paused.
+ fLeftMotor = 0.0f;
+ fRightMotor = 0.0f;
+ }
+
+ inputsystem->SetRumble( fLeftMotor, fRightMotor );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void StopAllRumbleEffects( void )
+{
+ g_RumbleEffects.StopAllEffects();
+
+ inputsystem->StopRumble();
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void RumbleEffect( unsigned char effectIndex, unsigned char rumbleData, unsigned char rumbleFlags )
+{
+ g_RumbleEffects.StartEffect( effectIndex, rumbleData, rumbleFlags );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void UpdateRumbleEffects()
+{
+ C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
+ if( !localPlayer || !localPlayer->IsAlive() )
+ {
+ StopAllRumbleEffects();
+ return;
+ }
+
+ g_RumbleEffects.UpdateEffects( gpGlobals->curtime );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void UpdateScreenShakeRumble( float shake, float balance )
+{
+ C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
+ if( !localPlayer || !localPlayer->IsAlive() )
+ {
+ return;
+ }
+
+ g_RumbleEffects.UpdateScreenShakeRumble( shake, balance );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void EnableRumbleOutput( bool bEnable )
+{
+ g_RumbleEffects.SetOutputEnabled( bEnable );
+}