//======= Copyright © 1996-2005, 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 );
}
