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 && i < NUM_WAVE_SAMPLES; j++ ) |
|
{ |
|
if( params.leftChannel ) |
|
{ |
|
pWaveform->amplitude_left[i++] = params.minAmplitude; |
|
} |
|
else |
|
{ |
|
pWaveform->amplitude_right[i++] = params.minAmplitude; |
|
} |
|
} |
|
for( j = 0 ; j < steps && i < NUM_WAVE_SAMPLES; 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 ); |
|
}
|
|
|