|
|
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "ivieweffects.h"
#include "shake.h"
#include "hud_macros.h"
#include "isaverestore.h"
#include "view_shared.h"
#include "iviewrender.h"
#include "viewrender.h"
#include "con_nprint.h"
#include "saverestoretypes.h"
#include "c_rumble.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern IntroData_t *g_pIntroData;
// Arbitrary limit so that bad entity logic on the server can't consume tons of memory on the client.
#define MAX_SHAKES 32
//-----------------------------------------------------------------------------
// Purpose: Screen fade variables
//-----------------------------------------------------------------------------
struct screenfade_t { float Speed; // How fast to fade (tics / second) (+ fade in, - fade out)
float End; // When the fading hits maximum
float Reset; // When to reset to not fading (for fadeout and hold)
byte r, g, b, alpha; // Fade color
int Flags; // Fading flags
DECLARE_SIMPLE_DATADESC(); };
BEGIN_SIMPLE_DATADESC( screenfade_t ) DEFINE_FIELD( Speed, FIELD_FLOAT ), DEFINE_FIELD( End, FIELD_TIME ), DEFINE_FIELD( Reset, FIELD_TIME ), DEFINE_FIELD( r, FIELD_CHARACTER ), DEFINE_FIELD( g, FIELD_CHARACTER ), DEFINE_FIELD( b, FIELD_CHARACTER ), DEFINE_FIELD( alpha, FIELD_CHARACTER ), DEFINE_FIELD( Flags, FIELD_INTEGER ), END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Screen shake variables
//-----------------------------------------------------------------------------
struct screenshake_t { float endtime; float duration; float amplitude; float frequency; float nextShake; Vector offset; float angle; int command; Vector direction; // used only by kSHAKE_DIRECTIONAL
// there are different types of screenshake --
// eventually these different types could become
// proper classes, but given the existing infrastructure
// for transmitting screenshakes, right now it's just
// easier to use an enum and switch.
enum ShakeType_t { kSHAKE_BASIC, ///< the original screenshake mechanism, a random offset selected every few frames.
kSHAKE_DIRECTIONAL, ///< a pseudo-damped-spring-ish punch to a specific screen space direction.
}; uint8 nShakeType; // actually a ShakeType_t, packed into eight bits (the datadesc system doesn't like bitfields)
screenshake_t() : nShakeType(kSHAKE_BASIC) {}; // nothing else is explicitly initialized
DECLARE_SIMPLE_DATADESC(); };
BEGIN_SIMPLE_DATADESC( screenshake_t ) DEFINE_FIELD( endtime, FIELD_TIME ), DEFINE_FIELD( duration, FIELD_FLOAT ), DEFINE_FIELD( amplitude, FIELD_FLOAT ), DEFINE_FIELD( frequency, FIELD_FLOAT ), DEFINE_FIELD( nextShake, FIELD_TIME ), DEFINE_FIELD( offset, FIELD_VECTOR ), DEFINE_FIELD( angle, FIELD_FLOAT ), DEFINE_FIELD( nShakeType, FIELD_CHARACTER ), DEFINE_FIELD( direction, FIELD_VECTOR ), END_DATADESC()
void CC_Shake_Stop(); //-----------------------------------------------------------------------------
// Purpose: Screen tilt variables
//-----------------------------------------------------------------------------
struct screentilt_t { bool easeInOut; QAngle angle; float starttime; float endtime; float duration; float tiltTime; Vector offset; int command;
DECLARE_SIMPLE_DATADESC(); };
BEGIN_SIMPLE_DATADESC( screentilt_t ) DEFINE_FIELD( angle, FIELD_VECTOR ), DEFINE_FIELD( starttime, FIELD_TIME ), DEFINE_FIELD( endtime, FIELD_TIME ), DEFINE_FIELD( duration, FIELD_FLOAT ), DEFINE_FIELD( tiltTime, FIELD_TIME ), DEFINE_FIELD( offset, FIELD_VECTOR ), END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Implements the view effects interface for the client .dll
//-----------------------------------------------------------------------------
class CViewEffects : public IViewEffects { public:
~CViewEffects() { ClearAllFades(); }
virtual void Init( void ); virtual void LevelInit( void ); virtual void GetFadeParams( byte *r, byte *g, byte *b, byte *a, bool *blend ); virtual void CalcShake( void ); virtual void ApplyShake( Vector& origin, QAngle& angles, float factor ); virtual void CalcTilt( void ); virtual void ApplyTilt( QAngle& angles, float factor );
virtual void Shake( const ScreenShake_t &data ); virtual void Tilt( ScreenTilt_t &data ); virtual void Fade( ScreenFade_t &data ); virtual void ClearPermanentFades( void ); virtual void FadeCalculate( void ); virtual void ClearAllFades( void );
// Save / Restore
virtual void Save( ISave *pSave ); virtual void Restore( IRestore *pRestore, bool fCreatePlayers );
CUserMessageBinder m_UMCMsgShake; CUserMessageBinder m_UMCMsgFade;
private:
void ClearAllShakes(); screenshake_t *FindLongestShake();
void ClearAllTilts(); screentilt_t *FindLongestTilt();
// helper subfunctions used inside CalcShake
void CalcShake_Basic( screenshake_t * pShake, float * RESTRICT pflRumbleAngle ); void CalcShake_Directional( screenshake_t * pShake, float * RESTRICT pflRumbleAngle );
CUtlVector<screenfade_t *> m_FadeList;
CUtlVector<screenshake_t *> m_ShakeList; Vector m_vecShakeAppliedOffset; float m_flShakeAppliedAngle;
CUtlVector<screentilt_t *> m_TiltList; QAngle m_vecTiltAppliedAngle;
int m_FadeColorRGBA[4]; bool m_bModulate;
friend void CC_Shake_Stop(); };
static CViewEffects g_ViewEffects[ MAX_SPLITSCREEN_PLAYERS ];
IViewEffects *GetViewEffects() { ASSERT_LOCAL_PLAYER_RESOLVABLE(); return &g_ViewEffects[ GET_ACTIVE_SPLITSCREEN_SLOT() ]; }
static CViewEffects &GetCViewEffects() { ASSERT_LOCAL_PLAYER_RESOLVABLE(); return g_ViewEffects[ GET_ACTIVE_SPLITSCREEN_SLOT() ]; }
// Callback function to call at end of screen m_Fade.
static int s_nCallbackParameter; static void ( *s_pfnFadeDoneCallback )( int parm1 );
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pszName -
// iSize -
// *pbuf -
// Output : static int
//-----------------------------------------------------------------------------
bool __MsgFunc_Shake( const CCSUsrMsg_Shake &msg ) { ScreenShake_t shake;
shake.command = (ShakeCommand_t)(msg.command()); shake.amplitude = msg.local_amplitude(); shake.frequency = msg.frequency(); shake.duration = msg.duration();
GetCViewEffects().Shake( shake );
return true; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pszName -
// iSize -
// *pbuf -
// Output : static int
//-----------------------------------------------------------------------------
void __MsgFunc_ShakeDir( bf_read &msg ) { ScreenShake_t shake;
shake.command = (ShakeCommand_t)msg.ReadByte(); shake.amplitude = msg.ReadFloat(); shake.frequency = msg.ReadFloat(); shake.duration = msg.ReadFloat(); msg.ReadBitVec3Normal( shake.direction );
GetCViewEffects().Shake( shake ); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pszName -
// iSize -
// *pbuf -
// Output : static int
//-----------------------------------------------------------------------------
void __MsgFunc_Tilt( bf_read &msg ) { ScreenTilt_t tilt;
Vector vecAngle;
tilt.command = msg.ReadByte(); tilt.easeInOut = msg.ReadByte() ? true : false; tilt.angle.x = msg.ReadFloat(); tilt.angle.y = msg.ReadFloat(); tilt.angle.z = msg.ReadFloat(); tilt.duration = msg.ReadFloat(); tilt.time = msg.ReadFloat();
GetCViewEffects().Tilt( tilt ); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pszName -
// iSize -
// *pbuf -
// Output : static int
//-----------------------------------------------------------------------------
bool __MsgFunc_Fade( const CCSUsrMsg_Fade &msg ) { ScreenFade_t fade;
fade.duration = msg.duration(); // fade lasts this long
fade.holdTime = msg.hold_time(); // fade lasts this long
fade.fadeFlags = msg.flags(); // fade type (in / out)
fade.r = msg.clr().r(); // fade red
fade.g = msg.clr().g(); // fade green
fade.b = msg.clr().b(); // fade blue
fade.a = msg.clr().a(); // fade blue
GetCViewEffects().Fade( fade );
return true; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CViewEffects::Init( void ) { HOOK_MESSAGE( Shake ); #ifdef INFESTED_DLL // the user message ShakeDir isn't registered for other games, but if you add it to your RegisterUserMessages, then you can un-#ifdef this
HOOK_MESSAGE( ShakeDir ); // directional screen shake
#endif
#ifdef HL2_CLIENT
// @TODO: Jeep, this causes assert in other games w/o this guard ifdef [6/3/2008 tom]
HOOK_MESSAGE( Tilt ); #endif
HOOK_MESSAGE( Fade ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CViewEffects::LevelInit( void ) { ClearAllShakes(); ClearAllTilts(); ClearAllFades(); }
static ConVar shake_show( "shake_show", "0", 0, "Displays a list of the active screen shakes." );
//-----------------------------------------------------------------------------
// Purpose: Stops all active screen shakes.
//-----------------------------------------------------------------------------
void CC_Shake_Stop() { GetCViewEffects().ClearAllShakes(); } static ConCommand shake_stop("shake_stop", CC_Shake_Stop, "Stops all active screen shakes.\n", FCVAR_CHEAT );
//-----------------------------------------------------------------------------
// Purpose: Test a punch-type screen shake
//-----------------------------------------------------------------------------
void CC_Shake_TestPunch( const CCommand &args ) { if ( args.ArgC() < 7 ) { Msg("Usage: %s x y z f a d\n" "where x,y,z are direction of screen punch\n" " f is frequency (1 means three bounces before settling)\n" " a is amplitude\n" " d is duration\n", args[0] ); }
const float x = atof( args[1] ); const float y = atof( args[2] ); const float z = atof( args[3] ); const float f = atof( args[4] ); const float a = atof( args[5] ); const float d = atof( args[6] );
ScreenShake_t shake; shake.command = SHAKE_START; shake.amplitude = a; shake.frequency = f; shake.duration = d; shake.direction = Vector(x,y,z);
GetCViewEffects().Shake(shake); }
static ConCommand shake_testpunch("shake_testpunch", CC_Shake_TestPunch, "Test a punch-style screen shake.\n", FCVAR_CHEAT );
//-----------------------------------------------------------------------------
// Purpose: Apply noise to the eye position.
// UNDONE: Feedback a bit of this into the view model position. It shakes too much
//-----------------------------------------------------------------------------
void CViewEffects::CalcShake( void ) {
// We'll accumulate the aggregate shake for this frame into these data members.
m_vecShakeAppliedOffset.Init(0, 0, 0); m_flShakeAppliedAngle = 0; float flRumbleAngle = 0;
bool bShow = shake_show.GetBool();
int nShakeCount = m_ShakeList.Count();
for ( int nShake = nShakeCount - 1; nShake >= 0; nShake-- ) { screenshake_t * RESTRICT pShake = m_ShakeList.Element( nShake );
if ( pShake->endtime == 0 ) { // Shouldn't be any such shakes in the list.
AssertMsg( false, "A screenshake has null endtime in CViewEffects::CalcShake\n" ); continue; }
if ( ( gpGlobals->curtime > pShake->endtime ) || pShake->duration <= 0 || pShake->amplitude <= 0 || pShake->frequency <= 0 ) { // Retire this shake.
delete m_ShakeList.Element( nShake ); m_ShakeList.FastRemove( nShake ); continue; }
if ( bShow ) { con_nprint_t np; np.time_to_live = 2.0f; np.fixed_width_font = true; np.color[0] = 1.0; np.color[1] = 0.8; np.color[2] = 0.1; np.index = nShake + 2;
engine->Con_NXPrintf( &np, "%02d: dur(%8.2f) amp(%8.2f) freq(%8.2f)", nShake + 1, (double)pShake->duration, (double)pShake->amplitude, (double)pShake->frequency ); }
// select the appropriate behavior based on screenshake type
switch ( pShake->nShakeType ) { case screenshake_t::kSHAKE_BASIC: CalcShake_Basic( pShake, &flRumbleAngle ); break; case screenshake_t::kSHAKE_DIRECTIONAL: CalcShake_Directional( pShake, &flRumbleAngle ); break; default: AssertMsg1( false, "Unknown shake type %d\n", pShake->nShakeType ); }
}
// Feed this to the rumble system!
UpdateScreenShakeRumble( XBX_GetActiveUserId(), flRumbleAngle ); }
void CViewEffects::CalcShake_Basic( screenshake_t * RESTRICT pShake, float * RESTRICT pflRumbleAngle ) { float fraction, freq;
if ( gpGlobals->curtime > pShake->nextShake ) { // Higher frequency means we recalc the extents more often and perturb the display again
pShake->nextShake = gpGlobals->curtime + (1.0f / pShake->frequency);
// Compute random shake extents (the shake will settle down from this)
for (int i = 0; i < 3; i++ ) { pShake->offset[i] = random->RandomFloat( -pShake->amplitude, pShake->amplitude ); }
pShake->angle = random->RandomFloat( -pShake->amplitude*0.25, pShake->amplitude*0.25 ); }
// Ramp down amplitude over duration (fraction goes from 1 to 0 linearly with slope 1/duration)
fraction = ( pShake->endtime - gpGlobals->curtime ) / pShake->duration;
// Ramp up frequency over duration
if ( fraction ) { freq = (pShake->frequency / fraction); } else { freq = 0; }
// square fraction to approach zero more quickly
fraction *= fraction;
// Sine wave that slowly settles to zero
float angle = gpGlobals->curtime * freq; if ( angle > 1e8 ) { angle = 1e8; } fraction = fraction * sin( angle );
if( pShake->command != SHAKE_START_NORUMBLE ) { // As long as this isn't a NO RUMBLE effect, then accumulate rumble
*pflRumbleAngle += pShake->angle * fraction; }
if( pShake->command != SHAKE_START_RUMBLEONLY ) { // As long as this isn't a RUMBLE ONLY effect, then accumulate screen shake
// Add to view origin
m_vecShakeAppliedOffset += pShake->offset * fraction;
// Add to roll
m_flShakeAppliedAngle += pShake->angle * fraction; }
// Drop amplitude a bit, less for higher frequency shakes
pShake->amplitude -= pShake->amplitude * ( gpGlobals->frametime / (pShake->duration * pShake->frequency) );
}
void CViewEffects::CalcShake_Directional( screenshake_t * RESTRICT pShake, float * RESTRICT pflRumbleAngle ) { // a screen punch follows an equation of the form
// y = sin(fx) * ( 1 - x / (3pi) ) for x=0..3pi
// where the duration is transformed to occupy
// the region 0..3pi
// and the frequency can be any number (which controls the number of oscillations
// before lapsing out)
// because of the resolution of this shake, it is performed every frame
// (ignores nextShake)
pShake->nextShake = gpGlobals->curtime + 0.001; float t = 1 - ( pShake->endtime - gpGlobals->curtime ) / pShake->duration; // t varies 0 .. 1 over life of shake
float fraction = ( 1 - t ); // compiler will hopefully elide the double subtraction
// transform the duration and so that x varies 0 .. 3PI over lifespan
t *= ( 3 * M_PI ); // t varies 0 .. 3PI
const float x = t * pShake->frequency; const float y = sin(x) * fraction;
// transform this -1..1 sinusoid by amplitude and direction
pShake->offset = pShake->direction * ( pShake->amplitude * y );
if( pShake->command != SHAKE_START_NORUMBLE ) { // As long as this isn't a NO RUMBLE effect, then accumulate rumble
*pflRumbleAngle += y; }
if( pShake->command != SHAKE_START_RUMBLEONLY ) { // As long as this isn't a RUMBLE ONLY effect, then accumulate screen shake
// Add to view origin
m_vecShakeAppliedOffset += pShake->offset ; } }
//-----------------------------------------------------------------------------
// Purpose: Apply the current screen shake to this origin/angles. Factor is the amount to apply
// This is so you can blend in part of the shake
// Input : origin -
// angles -
// factor -
//-----------------------------------------------------------------------------
void CViewEffects::ApplyShake( Vector& origin, QAngle& angles, float factor ) { VectorMA( origin, factor, m_vecShakeAppliedOffset, origin ); angles.z += m_flShakeAppliedAngle * factor; }
//-----------------------------------------------------------------------------
// Purpose: Apply noise to the eye position.
// UNDONE: Feedback a bit of this into the view model position. It shakes too much
//-----------------------------------------------------------------------------
void CViewEffects::CalcTilt( void ) { m_vecTiltAppliedAngle.Init();
int nTiltCount = m_TiltList.Count();
for ( int nTilt = nTiltCount - 1; nTilt >= 0; nTilt-- ) { screentilt_t *pTilt = m_TiltList.Element( nTilt );
if ( pTilt->endtime == 0 ) { // Shouldn't be any such tilts in the list.
Assert( false ); continue; }
if ( ( gpGlobals->curtime > pTilt->endtime ) || pTilt->duration <= 0 || pTilt->angle == QAngle( 0.0f, 0.0f, 0.0f ) ) { // Retire this tilt.
delete m_TiltList.Element( nTilt ); m_TiltList.FastRemove( nTilt ); continue; }
float flInterp = ( gpGlobals->curtime - pTilt->starttime ) / pTilt->tiltTime;
float flReturnInterp = ( pTilt->endtime - gpGlobals->curtime ) / pTilt->tiltTime;
if ( flReturnInterp < flInterp ) { flInterp = flReturnInterp; }
float flSmoothInterp = clamp( flInterp, 0.0f, 1.0f );
if ( pTilt->easeInOut ) { // Do a smooth ease in and out
flSmoothInterp = 1.0f - 0.5f * ( cosf( flSmoothInterp * M_PI ) + 1.0f ); }
// Accumulate world tilt
m_vecTiltAppliedAngle += pTilt->angle * flSmoothInterp; } }
//-----------------------------------------------------------------------------
// Purpose: Apply the current screen shake to this origin/angles. Factor is the amount to apply
// This is so you can blend in part of the shake
// Input : origin -
// angles -
// factor -
//-----------------------------------------------------------------------------
void CViewEffects::ApplyTilt( QAngle& angles, float factor ) { if ( m_vecTiltAppliedAngle == QAngle( 0.0f, 0.0f, 0.0f ) ) { // Fast out, no tilt to apply
return; }
matrix3x4_t matTilt; AngleIMatrix( m_vecTiltAppliedAngle, matTilt );
matrix3x4_t matToWorld; AngleMatrix( angles, matToWorld );
matrix3x4_t matTiltToWorld; ConcatTransforms( matTilt, matToWorld, matTiltToWorld);
Vector vecForwardTilted, vecUpTilted; VectorTransform( Vector( 1.0f, 0.0, 0.0f ), matTiltToWorld, vecForwardTilted ); VectorTransform( Vector( 0.0f, 0.0, 1.0f ), matTiltToWorld, vecUpTilted );
QAngle anglesTilted; VectorAngles( vecForwardTilted, vecUpTilted, anglesTilted );
angles = anglesTilted; }
//-----------------------------------------------------------------------------
// Purpose: Zeros out all active screen shakes.
//-----------------------------------------------------------------------------
void CViewEffects::ClearAllShakes() { int nShakeCount = m_ShakeList.Count(); for ( int i = 0; i < nShakeCount; i++ ) { delete m_ShakeList.Element( i ); }
m_ShakeList.Purge(); }
//-----------------------------------------------------------------------------
// Purpose: Returns the shake with the longest duration. This is the shake we
// use anytime we get an amplitude or frequency command, because the
// most likely case is that we're modifying a shake with a long
// duration rather than a brief shake caused by an explosion, etc.
//-----------------------------------------------------------------------------
screenshake_t *CViewEffects::FindLongestShake() { screenshake_t *pLongestShake = NULL;
int nShakeCount = m_ShakeList.Count(); for ( int i = 0; i < nShakeCount; i++ ) { screenshake_t *pShake = m_ShakeList.Element( i ); if ( pShake && ( !pLongestShake || ( pShake->duration > pLongestShake->duration ) ) ) { pLongestShake = pShake; } }
return pLongestShake; }
//-----------------------------------------------------------------------------
// Purpose: Message hook to parse ScreenShake messages
// Input : pszName -
// iSize -
// pbuf -
// Output :
//-----------------------------------------------------------------------------
void CViewEffects::Shake( const ScreenShake_t &data ) { if ( ( data.command == SHAKE_START || data.command == SHAKE_START_RUMBLEONLY ) && ( m_ShakeList.Count() < MAX_SHAKES ) ) { screenshake_t * RESTRICT pNewShake = new screenshake_t; // ugh, should just make these a static array
pNewShake->amplitude = data.amplitude; pNewShake->frequency = data.frequency; pNewShake->duration = data.duration; pNewShake->nextShake = 0; pNewShake->endtime = gpGlobals->curtime + data.duration; pNewShake->command = data.command; pNewShake->direction = data.direction; pNewShake->nShakeType = data.direction.IsZeroFast() ? screenshake_t::kSHAKE_BASIC : screenshake_t::kSHAKE_DIRECTIONAL;
m_ShakeList.AddToTail( (screenshake_t *) pNewShake ); } else if ( data.command == SHAKE_STOP) { ClearAllShakes(); } else if ( data.command == SHAKE_AMPLITUDE ) { // Look for the most likely shake to modify.
screenshake_t * RESTRICT pShake = FindLongestShake(); if ( pShake ) { pShake->amplitude = data.amplitude; } } else if ( data.command == SHAKE_FREQUENCY ) { // Look for the most likely shake to modify.
screenshake_t * RESTRICT pShake = FindLongestShake(); if ( pShake ) { pShake->frequency = data.frequency; } } }
//-----------------------------------------------------------------------------
// Purpose: Zeros out all active screen tilts.
//-----------------------------------------------------------------------------
void CViewEffects::ClearAllTilts() { int nTiltCount = m_TiltList.Count(); for ( int i = 0; i < nTiltCount; i++ ) { delete m_TiltList.Element( i ); }
m_TiltList.Purge(); }
//-----------------------------------------------------------------------------
// Purpose: Returns the shake with the longest duration. This is the shake we
// use anytime we get an amplitude or frequency command, because the
// most likely case is that we're modifying a shake with a long
// duration rather than a brief shake caused by an explosion, etc.
//-----------------------------------------------------------------------------
screentilt_t *CViewEffects::FindLongestTilt() { screentilt_t *pLongestTilt = NULL;
int nTiltCount = m_TiltList.Count(); for ( int i = 0; i < nTiltCount; i++ ) { screentilt_t *pTilt = m_TiltList.Element( i ); if ( pTilt && ( !pLongestTilt || ( pTilt->duration > pLongestTilt->duration ) ) ) { pLongestTilt = pTilt; } }
return pLongestTilt; }
//-----------------------------------------------------------------------------
// Purpose: Message hook to parse ScreenTilt messages
// Input : pszName -
// iSize -
// pbuf -
// Output :
//-----------------------------------------------------------------------------
void CViewEffects::Tilt( ScreenTilt_t &data ) { if ( ( data.command == SHAKE_START || data.command == SHAKE_START_RUMBLEONLY ) && ( m_ShakeList.Count() < MAX_SHAKES ) ) { screentilt_t *pNewTilt = new screentilt_t;
pNewTilt->easeInOut = data.easeInOut; pNewTilt->angle = data.angle; pNewTilt->duration = data.duration; pNewTilt->tiltTime = data.time; pNewTilt->starttime = gpGlobals->curtime; pNewTilt->endtime = pNewTilt->starttime + data.duration; pNewTilt->command = data.command;
m_TiltList.AddToTail( pNewTilt ); } else if ( data.command == SHAKE_STOP) { ClearAllTilts(); } }
//-----------------------------------------------------------------------------
// Purpose: Message hook to parse ScreenFade messages
// Input : *pszName -
// iSize -
// *pbuf -
// Output : int
//-----------------------------------------------------------------------------
void CViewEffects::Fade( ScreenFade_t &data ) { // Create a new fade and append it to the list
screenfade_t *pNewFade = new screenfade_t; pNewFade->End = data.duration * (1.0f/(float)(1<<SCREENFADE_FRACBITS)); pNewFade->Reset = data.holdTime * (1.0f/(float)(1<<SCREENFADE_FRACBITS)); pNewFade->r = data.r; pNewFade->g = data.g; pNewFade->b = data.b; pNewFade->alpha = data.a; pNewFade->Flags = data.fadeFlags; pNewFade->Speed = 0;
// Calc fade speed
if ( data.duration > 0 ) { if ( data.fadeFlags & FFADE_OUT ) { if ( pNewFade->End ) { pNewFade->Speed = -(float)pNewFade->alpha / pNewFade->End; }
pNewFade->End += gpGlobals->curtime; pNewFade->Reset += pNewFade->End; } else { if ( pNewFade->End ) { pNewFade->Speed = (float)pNewFade->alpha / pNewFade->End; }
pNewFade->Reset += gpGlobals->curtime; pNewFade->End += pNewFade->Reset; } }
if ( data.fadeFlags & FFADE_PURGE ) { ClearAllFades(); }
m_FadeList.AddToTail( pNewFade ); }
//-----------------------------------------------------------------------------
// Purpose: Compute the overall color & alpha of the fades
//-----------------------------------------------------------------------------
void CViewEffects::FadeCalculate( void ) { // Cycle through all fades and remove any that have finished (work backwards)
int i; int iSize = m_FadeList.Count(); for (i = iSize-1; i >= 0; i-- ) { screenfade_t *pFade = m_FadeList[i];
// Keep pushing reset time out indefinitely
if ( pFade->Flags & FFADE_STAYOUT ) { pFade->Reset = gpGlobals->curtime + 0.1; }
// All done?
if ( ( gpGlobals->curtime > pFade->Reset ) && ( gpGlobals->curtime > pFade->End ) ) { // User passed in a callback function, call it now
if ( s_pfnFadeDoneCallback ) { s_pfnFadeDoneCallback( s_nCallbackParameter ); s_pfnFadeDoneCallback = NULL; s_nCallbackParameter = 0; }
// Remove this Fade from the list
m_FadeList.FindAndRemove( pFade ); delete pFade; } }
m_bModulate = false; m_FadeColorRGBA[0] = m_FadeColorRGBA[1] = m_FadeColorRGBA[2] = m_FadeColorRGBA[3] = 0;
// Cycle through all fades in the list and calculate the overall color/alpha
for ( i = 0; i < m_FadeList.Count(); i++ ) { screenfade_t *pFade = m_FadeList[i];
// Color
m_FadeColorRGBA[0] += pFade->r; m_FadeColorRGBA[1] += pFade->g; m_FadeColorRGBA[2] += pFade->b;
// Fading...
int iFadeAlpha; if ( pFade->Flags & (FFADE_OUT|FFADE_IN) ) { iFadeAlpha = pFade->Speed * ( pFade->End - gpGlobals->curtime ); if ( pFade->Flags & FFADE_OUT ) { iFadeAlpha += pFade->alpha; } iFadeAlpha = MIN( iFadeAlpha, pFade->alpha ); iFadeAlpha = MAX( 0, iFadeAlpha ); } else { iFadeAlpha = pFade->alpha; }
// Use highest alpha
if ( iFadeAlpha > m_FadeColorRGBA[3] ) { m_FadeColorRGBA[3] = iFadeAlpha; }
// Modulate?
if ( pFade->Flags & FFADE_MODULATE ) { m_bModulate = true; } }
// Divide colors
if ( m_FadeList.Count() ) { m_FadeColorRGBA[0] /= m_FadeList.Count(); m_FadeColorRGBA[1] /= m_FadeList.Count(); m_FadeColorRGBA[2] /= m_FadeList.Count(); } }
//-----------------------------------------------------------------------------
// Purpose: Clear only the permanent fades in our fade list
//-----------------------------------------------------------------------------
void CViewEffects::ClearPermanentFades( void ) { int iSize = m_FadeList.Count(); for (int i = iSize-1; i >= 0; i-- ) { screenfade_t *pFade = m_FadeList[i];
if ( pFade->Flags & FFADE_STAYOUT ) { // Destroy this fade
m_FadeList.FindAndRemove( pFade ); delete pFade; } } }
//-----------------------------------------------------------------------------
// Purpose: Purge & delete all fades in the queue
//-----------------------------------------------------------------------------
void CViewEffects::ClearAllFades( void ) { int iSize = m_FadeList.Count(); for (int i = iSize-1; i >= 0; i-- ) { delete m_FadeList[i]; } m_FadeList.Purge(); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : context - Which call to Render is this ( CViewSetup::context )
// *r -
// *g -
// *b -
// *a -
// *blend -
//-----------------------------------------------------------------------------
void CViewEffects::GetFadeParams( byte *r, byte *g, byte *b, byte *a, bool *blend ) { // If the intro is overriding our fade, use that instead
if ( g_pIntroData && g_pIntroData->m_flCurrentFadeColor[3] ) { *r = g_pIntroData->m_flCurrentFadeColor[0]; *g = g_pIntroData->m_flCurrentFadeColor[1]; *b = g_pIntroData->m_flCurrentFadeColor[2]; *a = g_pIntroData->m_flCurrentFadeColor[3]; *blend = false; return; }
FadeCalculate();
*r = m_FadeColorRGBA[0]; *g = m_FadeColorRGBA[1]; *b = m_FadeColorRGBA[2]; *a = m_FadeColorRGBA[3]; *blend = m_bModulate; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSave -
//-----------------------------------------------------------------------------
void CViewEffects::Save( ISave *pSave ) { // Save the view fades
int iCount = m_FadeList.Count(); pSave->WriteInt( &iCount ); for ( int i = 0; i < iCount; i++ ) { pSave->StartBlock(); pSave->WriteAll( m_FadeList[i] ); pSave->EndBlock(); }
// Save the view shakes
iCount = m_ShakeList.Count(); pSave->WriteInt( &iCount ); for ( int i = 0; i < iCount; i++ ) { pSave->StartBlock(); pSave->WriteAll( m_ShakeList[i] ); pSave->EndBlock(); } }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pRestore -
// fCreatePlayers -
//-----------------------------------------------------------------------------
void CViewEffects::Restore( IRestore *pRestore, bool fCreatePlayers ) { CGameSaveRestoreInfo *pSaveData = pRestore->GetGameSaveRestoreInfo();
// View effects is a singleton so we only need to restore it once,
// from the level that we are going into.
if( !pSaveData->levelInfo.fUseLandmark ) { ClearAllFades(); ClearAllShakes();
// Read in the view fades
int iCount = pRestore->ReadInt(); for ( int i = 0; i < iCount; i++ ) { screenfade_t *pNewFade = new screenfade_t;
pRestore->StartBlock(); pRestore->ReadAll( pNewFade ); pRestore->EndBlock();
m_FadeList.AddToTail( pNewFade ); }
// Read in the view shakes
iCount = pRestore->ReadInt(); for ( int i = 0; i < iCount; i++ ) { screenshake_t *pNewShake = new screenshake_t;
pRestore->StartBlock(); pRestore->ReadAll( pNewShake ); pRestore->EndBlock();
m_ShakeList.AddToTail( pNewShake ); } } }
//====================================================================================================
// CLIENTSIDE VIEW EFFECTS SAVE/RESTORE
//====================================================================================================
static short VIEWEFFECTS_SAVE_RESTORE_VERSION = 2;
class CViewEffectsSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler { struct QueuedItem_t; public: CViewEffectsSaveRestoreBlockHandler() { }
const char *GetBlockName() { return "ViewEffects"; }
//---------------------------------
virtual void PreSave( CSaveRestoreData * ) { } //---------------------------------
virtual void Save( ISave *pSave ) { ACTIVE_SPLITSCREEN_PLAYER_GUARD( 0 ); GetViewEffects()->Save( pSave ); } //---------------------------------
virtual void WriteSaveHeaders( ISave *pSave ) { pSave->WriteShort( &VIEWEFFECTS_SAVE_RESTORE_VERSION ); } //---------------------------------
virtual void PostSave() { } //---------------------------------
virtual void PreRestore() { } //---------------------------------
virtual void ReadRestoreHeaders( IRestore *pRestore ) { // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so.
short version = pRestore->ReadShort(); m_bDoLoad = ( version == VIEWEFFECTS_SAVE_RESTORE_VERSION ); }
//---------------------------------
virtual void Restore( IRestore *pRestore, bool fCreatePlayers ) { if ( m_bDoLoad ) { ACTIVE_SPLITSCREEN_PLAYER_GUARD( 0 ); GetViewEffects()->Restore( pRestore, fCreatePlayers ); } } //---------------------------------
virtual void PostRestore() { } private: bool m_bDoLoad; };
//-----------------------------------------------------------------------------
CViewEffectsSaveRestoreBlockHandler g_ViewEffectsSaveRestoreBlockHandler;
ISaveRestoreBlockHandler *GetViewEffectsRestoreBlockHandler() { return &g_ViewEffectsSaveRestoreBlockHandler; }
|