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.
826 lines
22 KiB
826 lines
22 KiB
//========= 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 ¶ms )
|
|
{
|
|
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 ¶ms )
|
|
{
|
|
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 ¶ms )
|
|
{
|
|
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 ¶ms )
|
|
{
|
|
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 );
|
|
}
|