source-engine/utils/xbox/AppChooser/main.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

2240 lines
61 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: X360 Application Chooser
//
//===========================================================================//
#if !defined( _X360 )
#include <windows.h>
#endif
#include "appframework/iappsystemgroup.h"
#include "appframework/appframework.h"
#include "tier0/dbg.h"
#include "tier1/interface.h"
#include "tier1/KeyValues.h"
#include "filesystem.h"
#include "vstdlib/cvar.h"
#include "filesystem_init.h"
#include "tier1/utlbuffer.h"
#include "icommandline.h"
#include "datacache/idatacache.h"
#include "datacache/imdlcache.h"
#include "studio.h"
#include "utlbuffer.h"
#include "tier2/utlstreambuffer.h"
#include "tier2/tier2.h"
#include "appframework/tier3app.h"
#include "mathlib/mathlib.h"
#include "inputsystem/iinputsystem.h"
#include "vphysics_interface.h"
#include "istudiorender.h"
#include "studio.h"
#include "vgui/IVGui.h"
#include "VGuiMatSurface/IMatSystemSurface.h"
#include "matsys_controls/matsyscontrols.h"
#include "vgui/ILocalize.h"
#include "vgui_controls/panel.h"
#include "vgui_controls/Label.h"
#include "vgui_controls/imagepanel.h"
#include "vgui_controls/AnimationController.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/imesh.h"
#include "materialsystem/materialsystem_config.h"
#include "materialsystem/MaterialSystemUtil.h"
#include "materialsystem/ishaderapi.h"
#include "materialsystem/itexture.h"
#include "filesystem/IQueuedLoader.h"
#include "xwvfile.h"
#if defined( _X360 )
#include "hl2orange.spa.h"
#include "xbox/xbox_console.h"
#include "xbox/xbox_win32stubs.h"
#include "xbox/xbox_launch.h"
#include <xaudio.h>
#include <xmedia.h>
#include <xmp.h>
#endif
#if !defined( _X360 )
#include "xbox/xboxstubs.h"
#endif
#include "tier0/memdbgon.h"
#define INACTIVITY_TIMEOUT 120
#define MOVIE_PATH "d:\\movies"
bool g_bActive = true;
// user background music can force this to be zero
static float g_TargetMovieVolume = 1.0f;
extern SpewOutputFunc_t g_DefaultSpewFunc;
typedef void (*MovieEndCallback_t)();
struct game_t
{
const wchar_t *pName;
const char *pGameDir;
bool bEnabled;
};
game_t g_Games[] =
{
{ L"HALF-LIFE 2", "hl2", true },
{ L"HALF-LIFE 2:\nEPISODE ONE", "episodic", true },
{ L" HALF-LIFE 2:\nEPISODE TWO", "ep2", true },
{ L"PORTAL", "portal", true },
{ L" TEAM\nFORTRESS 2", "tf", true },
};
struct StartupMovie_t
{
const char *pMovieName;
bool bUserCanSkip;
};
// played in order on initial startup
StartupMovie_t g_StartupMovies[] =
{
{ "valve_legalese.wmv", false },
};
const char *g_DemoMovies[] =
{
"demo.wmv",
"teaser_l4d.wmv",
};
class CImage : public vgui::Panel
{
DECLARE_CLASS_SIMPLE( CImage, vgui::Panel );
public:
CImage( vgui::Panel *pParent, const char *pName, const char *pImageName ) : BaseClass( pParent, pName )
{
m_imageID = vgui::surface()->CreateNewTextureID();
vgui::surface()->DrawSetTextureFile( m_imageID, pImageName, false, false );
m_Color = Color( 255, 255, 255, 255 );
SetPaintBackgroundEnabled( true );
m_AspectRatio = 1.0f;
m_bLetterbox = false;
}
CImage( vgui::Panel *pParent, const char *pName, IMaterial *pMaterial ) : BaseClass( pParent, pName )
{
m_imageID = vgui::surface()->CreateNewTextureID();
g_pMatSystemSurface->DrawSetTextureMaterial( m_imageID, pMaterial );
m_Color = Color( 255, 255, 255, 255 );
SetPaintBackgroundEnabled( true );
m_AspectRatio = 1.0f;
m_bLetterbox = false;
}
void SetAspectRatio( float aspectRatio, bool bLetterbox )
{
if ( aspectRatio > 1.0f )
{
m_AspectRatio = aspectRatio;
m_bLetterbox = bLetterbox;
}
}
void SetColor( int r, int g, int b )
{
// set rgb only
m_Color.SetColor( r, g, b, m_Color.a() );
}
void SetAlpha( int alpha )
{
// set alpha only
alpha = clamp( alpha, 0, 255 );
m_Color.SetColor( m_Color.r(), m_Color.g(), m_Color.b(), alpha );
}
int GetAlpha() const
{
return m_Color.a();
}
virtual void PaintBackground( void )
{
if ( m_Color.a() != 0 )
{
int panelWidth, panelHeight;
GetSize( panelWidth, panelHeight );
float s0 = 0.0f;
float s1 = 1.0f;
int y = 0;
if ( m_AspectRatio > 1.0f )
{
if ( m_bLetterbox )
{
// horizontal letterbox
float adjustedHeight = (float)panelWidth / m_AspectRatio;
float bandHeight = ( (float)panelHeight - adjustedHeight ) / 2;
vgui::surface()->DrawSetColor( Color( 0, 0, 0, m_Color.a() ) );
vgui::surface()->DrawFilledRect( 0, 0, panelWidth, bandHeight );
vgui::surface()->DrawFilledRect( 0, panelHeight - bandHeight, panelWidth, panelHeight );
y = bandHeight;
panelHeight = adjustedHeight;
}
else
{
// hold the panel's height constant, determine the corresponding aspect corrected image width
float imageWidth = (float)panelHeight * m_AspectRatio;
// adjust the image width as a percentage of the panel's width
// scale and center;
s1 = (float)panelWidth / imageWidth;
s0 = ( 1 - s1 ) / 2.0f;
s1 = s0 + s1;
}
}
vgui::surface()->DrawSetColor( m_Color );
vgui::surface()->DrawSetTexture( m_imageID );
vgui::surface()->DrawTexturedSubRect( 0, y, panelWidth, y+panelHeight, s0, 0.0f, s1, 1.0f );
}
}
int m_imageID;
Color m_Color;
float m_AspectRatio;
bool m_bLetterbox;
};
class CMovieImage : public vgui::Panel
{
DECLARE_CLASS_SIMPLE( CMovieImage, vgui::Panel );
public:
CMovieImage( vgui::Panel *pParent, const char *pName, const char *pUniqueName, int nDecodeWidth, int nDecodeHeight, float fadeDelay = 1.0f ) : BaseClass( pParent, pName )
{
// decoupled from actual panel bounds, can be decoded at any power of two resolution
m_nDecodeWidth = nDecodeWidth;
m_nDecodeHeight = nDecodeHeight;
char materialName[MAX_PATH];
V_snprintf( materialName, sizeof( materialName ), "MovieMaterial_%s.vmt", pUniqueName );
char textureName[MAX_PATH];
V_snprintf( textureName, sizeof( textureName ), "MovieFrame_%s", pUniqueName );
// create a texture for use as movie frame grab
m_pTexture = g_pMaterialSystem->CreateProceduralTexture(
textureName,
TEXTURE_GROUP_OTHER,
m_nDecodeWidth,
m_nDecodeHeight,
g_pMaterialSystem->GetBackBufferFormat(),
TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY );
KeyValues *pVMTKeyValues = new KeyValues( "screenspace_general" );
pVMTKeyValues->SetInt( "$ignorez", 1 );
pVMTKeyValues->SetInt( "$linearread_basetexture", 1 );
pVMTKeyValues->SetInt( "$X360APPCHOOSER", 1 );
pVMTKeyValues->SetString( "$PIXSHADER", "appchooser360movie_ps20b" );
pVMTKeyValues->SetString( "$basetexture", textureName );
m_pMaterial = g_pMaterialSystem->CreateMaterial( materialName, pVMTKeyValues );
m_imageID = vgui::surface()->CreateNewTextureID();
g_pMatSystemSurface->DrawSetTextureMaterial( m_imageID, m_pMaterial );
SetPaintBackgroundEnabled( true );
// initially invisible
m_Color = Color( 255, 255, 255, 0 );
m_FadeDelay = fadeDelay;
m_pXMVPlayer = NULL;
m_bStopping = false;
m_StartTime = 0;
m_StopTime = 0;
m_pMovieEndCallback = NULL;
m_bLooped = false;
m_AspectRatio = 1.0f;
m_bLetterbox = false;
}
void SetAspectRatio( float aspectRatio, bool bLetterbox )
{
if ( aspectRatio > 1.0f )
{
m_AspectRatio = aspectRatio;
m_bLetterbox = bLetterbox;
}
}
void SetColor( int r, int g, int b )
{
// set rgb only
m_Color.SetColor( r, g, b, m_Color.a() );
}
// -1 means never have any audio, otherwise set to current target
void InitUserAudioMix( bool bAudio )
{
m_CurrentVolume = bAudio ? g_TargetMovieVolume : -1;
}
// fade in/out based on user interaction with dashboard music system
void UpdateMovieVolume( bool bForce, float frametime = 1.0f )
{
// m_CurrentVolume < 0 means this movie never plays audio
if ( !m_pXMVPlayer || m_CurrentVolume < 0 )
return;
// forced update or new volume, ramp & set
if ( bForce || g_TargetMovieVolume != m_CurrentVolume )
{
if ( bForce )
{
frametime = 1.0f;
}
m_CurrentVolume = Approach( g_TargetMovieVolume, m_CurrentVolume, frametime * 0.5f );
// UNDONE: Under what conditions can this fail? If it fails it could cause audible pops
IXAudioSourceVoice *pVoice = NULL;
HRESULT hr = m_pXMVPlayer->GetSourceVoice( &pVoice );
if ( !FAILED( hr ) && pVoice )
{
pVoice->SetVolume( m_CurrentVolume );
}
}
}
bool StartMovieFromMemory( const void *pBuffer, int bufferSize, bool bAudio, bool bLoop, MovieEndCallback_t pMovieEndCallback = NULL )
{
if ( m_pXMVPlayer || m_bStopping )
{
// already started or currently stopping
return false;
}
if ( !pBuffer || !bufferSize )
{
return false;
}
XMEDIA_XMV_CREATE_PARAMETERS xmvParameters;
V_memset( &xmvParameters, 0, sizeof( xmvParameters ) );
xmvParameters.dwFlags = XMEDIA_CREATE_CPU_AFFINITY;
if ( bLoop )
{
xmvParameters.dwFlags |= XMEDIA_CREATE_FOR_LOOP;
m_bLooped = true;
}
if ( !bAudio )
{
xmvParameters.dwAudioStreamId = (DWORD)XMEDIA_STREAM_ID_DONT_USE;
}
InitUserAudioMix(bAudio);
xmvParameters.dwVideoDecoderCpu = 2;
xmvParameters.dwVideoRendererCpu = 2;
xmvParameters.dwAudioDecoderCpu = 4;
xmvParameters.dwAudioRendererCpu = 4;
xmvParameters.createType = XMEDIA_CREATE_FROM_MEMORY;
xmvParameters.createFromMemory.pvBuffer = (PVOID)pBuffer;
xmvParameters.createFromMemory.dwBufferSize = bufferSize;
IDirect3DDevice9 *pD3DDevice = (IDirect3DDevice9 *)g_pMaterialSystem->GetD3DDevice();
HRESULT hr = XMediaCreateXmvPlayer( pD3DDevice, &xmvParameters, &m_pXMVPlayer );
if ( FAILED( hr ) )
{
return false;
}
m_pMovieEndCallback = pMovieEndCallback;
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = m_nDecodeWidth;
rect.bottom = m_nDecodeHeight;
m_pXMVPlayer->SetRectangle( &rect );
UpdateMovieVolume( true );
SetAlpha( 0 );
m_StartTime = Plat_FloatTime() + m_FadeDelay;
m_StopTime = 0;
return true;
}
bool StartMovieFromFile( const char *pMovieName, bool bAudio, bool bLoop, MovieEndCallback_t pMovieEndCallback = NULL )
{
if ( m_pXMVPlayer || m_bStopping )
{
// already started or currently stopping
return false;
}
XMEDIA_XMV_CREATE_PARAMETERS xmvParameters;
V_memset( &xmvParameters, 0, sizeof( xmvParameters ) );
xmvParameters.dwFlags = XMEDIA_CREATE_CPU_AFFINITY;
if ( bLoop )
{
xmvParameters.dwFlags |= XMEDIA_CREATE_FOR_LOOP;
m_bLooped = true;
}
if ( !bAudio )
{
xmvParameters.dwAudioStreamId = (DWORD)XMEDIA_STREAM_ID_DONT_USE;
}
InitUserAudioMix( bAudio );
char szFilename[MAX_PATH];
V_ComposeFileName( MOVIE_PATH, pMovieName, szFilename, sizeof( szFilename ) );
xmvParameters.dwVideoDecoderCpu = 2;
xmvParameters.dwVideoRendererCpu = 2;
xmvParameters.dwAudioDecoderCpu = 4;
xmvParameters.dwAudioRendererCpu = 4;
xmvParameters.createType = XMEDIA_CREATE_FROM_FILE;
xmvParameters.createFromFile.szFileName = szFilename;
IDirect3DDevice9 *pD3DDevice = (IDirect3DDevice9 *)g_pMaterialSystem->GetD3DDevice();
HRESULT hr = XMediaCreateXmvPlayer( pD3DDevice, &xmvParameters, &m_pXMVPlayer );
if ( FAILED( hr ) )
{
return false;
}
m_pMovieEndCallback = pMovieEndCallback;
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = m_nDecodeWidth;
rect.bottom = m_nDecodeHeight;
m_pXMVPlayer->SetRectangle( &rect );
UpdateMovieVolume( true );
SetAlpha( 0 );
m_StartTime = Plat_FloatTime() + m_FadeDelay;
m_StopTime = 0;
return true;
}
void StopMovie()
{
if ( !m_pXMVPlayer || m_bStopping )
{
// already stopped or currently stopping
return;
}
m_bLooped = false;
m_bStopping = true;
m_pXMVPlayer->Stop( XMEDIA_STOP_IMMEDIATE );
SetAlpha( 255 );
m_StartTime = 0;
m_StopTime = Plat_FloatTime() + m_FadeDelay;
}
virtual void PaintBackground( void )
{
if ( m_StartTime )
{
// fade up goes from [0..1] and holds on 1
float t = ( Plat_FloatTime() - m_StartTime ) * 2.0f;
t = clamp( t, 0.0f, 1.0f );
SetAlpha( t * 255.0f );
}
if ( m_StopTime )
{
// fade out goes from [1..0] and holds on 0
float t = ( Plat_FloatTime() - m_StopTime ) * 2.0f;
t = 1.0f - clamp( t, 0.0f, 1.0f );
SetAlpha( t * 255.0f );
if ( m_Color.a() == 0 )
{
if ( m_bStopping && m_pMovieEndCallback )
{
m_pMovieEndCallback();
}
m_bStopping = false;
}
}
if ( m_Color.a() != 0 )
{
int panelWidth, panelHeight;
GetSize( panelWidth, panelHeight );
float s0 = 0.0f;
float s1 = 1.0f;
int y = 0;
if ( m_AspectRatio > 1.0f )
{
if ( m_bLetterbox )
{
// horizontal letterbox
float adjustedHeight = (float)panelWidth / m_AspectRatio;
float bandHeight = ( (float)panelHeight - adjustedHeight ) / 2;
vgui::surface()->DrawSetColor( Color( 0, 0, 0, m_Color.a() ) );
vgui::surface()->DrawFilledRect( 0, 0, panelWidth, bandHeight );
vgui::surface()->DrawFilledRect( 0, panelHeight - bandHeight, panelWidth, panelHeight );
y = bandHeight;
panelHeight = adjustedHeight;
}
else
{
// hold the panel's height constant, determine the corresponding aspect corrected image width
float imageWidth = (float)panelHeight * m_AspectRatio;
// adjust the image width as a percentage of the panel's width
// scale and center;
s1 = (float)panelWidth / imageWidth;
s0 = ( 1 - s1 ) / 2.0f;
s1 = s0 + s1;
}
}
vgui::surface()->DrawSetColor( m_Color );
vgui::surface()->DrawSetTexture( m_imageID );
vgui::surface()->DrawTexturedSubRect( 0, y, panelWidth, y+panelHeight, s0, 0.0f, s1, 1.0f );
}
}
bool IsFullyReleased()
{
// fully stopped and released when object no longer exists
return ( m_pXMVPlayer == NULL ) && ( m_bStopping == false );
}
bool RenderVideoFrame()
{
if ( !m_pXMVPlayer )
{
return false;
}
// If RenderNextFrame does not return S_OK then the frame was not
// rendered (perhaps because it was cancelled) so a regular frame
// buffer should be rendered before calling present.
bool bRenderedFrame = true;
HRESULT hr = m_pXMVPlayer->RenderNextFrame( 0, NULL );
if ( FAILED( hr ) || hr == XMEDIA_W_EOF )
{
bRenderedFrame = false;
if ( !m_bLooped )
{
// Release the movie object
m_pXMVPlayer->Release();
m_pXMVPlayer = NULL;
if ( !m_bStopping )
{
m_bStopping = true;
SetAlpha( 255 );
m_StartTime = 0;
m_StopTime = Plat_FloatTime() + m_FadeDelay;
}
}
}
// UNDONE: Need a frametime here. Assume it's 30fps.
// NOTE: This is only used to time audio fades, so if it's wrong by 2X it's not
// going to be noticeable. Probably fine to ship this.
UpdateMovieVolume( false, 1.0f / 30.0f );
// Reset our cached view of what pixel and vertex shaders are set, because
// it is no longer accurate, since XMV will have set their own shaders.
// This avoids problems when the shader cache thinks it knows what shader
// is set and it is wrong.
IDirect3DDevice9 *pD3DDevice = (IDirect3DDevice9 *)g_pMaterialSystem->GetD3DDevice();
pD3DDevice->SetVertexShader( NULL );
pD3DDevice->SetPixelShader( NULL );
pD3DDevice->SetVertexDeclaration( NULL );
pD3DDevice->SetRenderState( D3DRS_VIEWPORTENABLE, TRUE );
if ( bRenderedFrame )
{
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
Rect_t rect;
rect.x = 0;
rect.y = 0;
rect.width = m_nDecodeWidth;
rect.height = m_nDecodeHeight;
pRenderContext->CopyRenderTargetToTextureEx( m_pTexture, 0, &rect );
}
return bRenderedFrame;
}
private:
void SetAlpha( int alpha )
{
// set alpha only
alpha = clamp( alpha, 0, 255 );
m_Color.SetColor( m_Color.r(), m_Color.g(), m_Color.b(), alpha );
}
int m_imageID;
Color m_Color;
IXMediaXmvPlayer *m_pXMVPlayer;
ITexture *m_pTexture;
IMaterial *m_pMaterial;
bool m_bStopping;
bool m_bLooped;
float m_StartTime;
float m_StopTime;
int m_nDecodeWidth;
int m_nDecodeHeight;
float m_FadeDelay;
float m_AspectRatio;
float m_CurrentVolume;
bool m_bLetterbox;
MovieEndCallback_t m_pMovieEndCallback;
};
class CShadowLabel : public vgui::Label
{
DECLARE_CLASS_SIMPLE( CShadowLabel, vgui::Label );
public:
CShadowLabel( vgui::Panel *pParent, const char *pName, const wchar_t *pText ) : BaseClass( pParent, pName, pText )
{
}
virtual void Paint( void )
{
BaseClass::Paint();
BaseClass::Paint();
BaseClass::Paint();
BaseClass::Paint();
BaseClass::Paint();
}
};
class CGamePanel : public vgui::Panel
{
DECLARE_CLASS_SIMPLE( CGamePanel, vgui::Panel );
public:
CGamePanel( vgui::Panel *pParent, const char *pName, game_t *pGame, bool bIsWidescreen, KeyValues *pKVSettings ) : BaseClass( pParent, pName )
{
m_bEnabled = pGame->bEnabled;
vgui::HScheme hScheme = vgui::scheme()->GetScheme( "SourceScheme" );
vgui::IScheme *pSourceScheme = vgui::scheme()->GetIScheme( hScheme );
KeyValues *pKV = pKVSettings->FindKey( "GamePanel" );
int panelWidth = pKV->GetInt( "wide" );
int panelHeight = pKV->GetInt( "tall" );
SetSize( panelWidth, panelHeight );
// thumbnail static image
char szFilename[MAX_PATH];
V_snprintf( szFilename, sizeof( szFilename ), "vgui/appchooser/%s", pGame->pGameDir );
pKV = pKVSettings->FindKey( "GameImage" );
int y = pKV->GetInt( "ypos" );
int w = pKV->GetInt( "wide" );
int h = pKV->GetInt( "tall" );
int x = ( panelWidth - w ) / 2;
m_pThumbImage = new CImage( this, "GameImage", szFilename );
SETUP_PANEL( m_pThumbImage );
m_pThumbImage->SetBounds( x, y, w, h );
m_pThumbImage->SetAspectRatio( bIsWidescreen ? 1.0f : 1.778f, false );
m_pThumbImage->SetVisible( true );
// thumbnail movie
m_pMovieImage = new CMovieImage( this, "Movie", pGame->pGameDir, 256, 256, 0.2f );
SETUP_PANEL( m_pMovieImage );
m_pMovieImage->SetBounds( x, y, w, h );
m_pMovieImage->SetVisible( true );
V_snprintf( szFilename, sizeof( szFilename ), "%s\\thumb_%s.wmv", MOVIE_PATH, pGame->pGameDir );
if ( !g_pFullFileSystem->ReadFile( szFilename, NULL, m_MovieBuffer ) )
{
m_MovieBuffer.Purge();
}
// game title shadow
pKV = pKVSettings->FindKey( "GameTitle" );
y = pKV->GetInt( "ypos" );
m_pTitleShadow = new CShadowLabel( this, "GameTitle", pGame->pName );
SETUP_PANEL( m_pTitleShadow );
m_pTitleShadow->SetVisible( true );
m_pTitleShadow->SetFont( pSourceScheme->GetFont( "AppChooserGameTitleFontBlur" ) );
m_pTitleShadow->SizeToContents();
m_pTitleShadow->SetPos( 0, y );
m_pTitleShadow->SetWide( panelWidth );
m_pTitleShadow->SetContentAlignment( vgui::Label::a_center );
m_pTitleShadow->SetPaintBackgroundEnabled( false );
m_pTitleShadow->SetFgColor( Color( 0, 0, 0, 255 ) );
// game title
m_pTitle = new vgui::Label( this, "GameTitle", pGame->pName );
SETUP_PANEL( m_pTitle );
m_pTitle->SetVisible( true );
m_pTitle->SetFont( pSourceScheme->GetFont( "AppChooserGameTitleFont" ) );
m_pTitle->SizeToContents();
m_pTitle->SetPos( 0, y );
m_pTitle->SetWide( panelWidth );
m_pTitle->SetContentAlignment( vgui::Label::a_center );
m_pTitle->SetPaintBackgroundEnabled( false );
m_pTitle->SetFgColor( Color( 255, 255, 255, 255 ) );
// button bounds
vgui::HFont hFont = pSourceScheme->GetFont( "GameUIButtons" );
pKV = pKVSettings->FindKey( "GameButton" );
y = panelHeight - vgui::surface()->GetFontTall( hFont ) - pKV->GetInt( "ypos" );
m_pButtonText = new vgui::Label( this, "GameButton", g_pVGuiLocalize->Find( "#GameUI_Icons_A_BUTTON" ) );
SETUP_PANEL( m_pButtonText );
m_pButtonText->SetVisible( false );
m_pButtonText->SetFont( hFont );
m_pButtonText->SizeToContents();
m_pButtonText->SetPos( 0, y );
m_pButtonText->SetWide( panelWidth );
m_pButtonText->SetContentAlignment( vgui::Label::a_center );
m_pButtonText->SetPaintBackgroundEnabled( false );
m_pButtonText->SetFgColor( Color( 255, 255, 255, 255 ) );
}
~CGamePanel( void )
{
}
void SetSelected( bool bSelected )
{
m_pButtonText->SetVisible( m_bEnabled ? bSelected : false );
if ( bSelected )
{
SetBgColor( Color( 190, 115, 0, 128 ) );
m_pThumbImage->SetColor( 255, 255, 255 );
m_pMovieImage->SetColor( 255, 255, 255 );
m_pTitle->SetFgColor( Color( 255, 255, 255, 255 ) );
}
else
{
SetBgColor( Color( 160, 160, 160, 50 ) );
m_pThumbImage->SetColor( 100, 100, 100 );
m_pMovieImage->SetColor( 120, 120, 120 );
m_pTitle->SetFgColor( Color( 140, 140, 140, 255 ) );
}
}
void StartMovie()
{
m_pMovieImage->StartMovieFromMemory( m_MovieBuffer.Base(), m_MovieBuffer.TellMaxPut(), true, true );
}
void StopMovie()
{
m_pMovieImage->StopMovie();
}
CImage *m_pThumbImage;
CMovieImage *m_pMovieImage;
CShadowLabel *m_pTitleShadow;
vgui::Label *m_pTitle;
vgui::Label *m_pButtonText;
CUtlBuffer m_MovieBuffer;
bool m_bEnabled;
int m_imageID;
};
//-----------------------------------------------------------------------------
// Simple XAudio wrapper to instance a sound. Provides lightweight audio feedback for ui.
// Instance this per sound.
//-----------------------------------------------------------------------------
class CXSound
{
public:
CXSound()
{
for ( int i=0; i<ARRAYSIZE( m_pSourceVoices ); i++ )
{
m_pSourceVoices[i] = NULL;
}
m_pXMAData = NULL;
m_nXMADataSize = 0;
m_numVoices = 0;
}
~CXSound()
{
Stop();
Release();
}
//-----------------------------------------------------------------------------
// Setup the wave, caller can clamp the number of simultaneous playing instances
// of this sound. Useful for ui clicks to keep up with rapid input.
//-----------------------------------------------------------------------------
bool SetupWave( const char *pFilename, int numSimultaneous = 1 )
{
CUtlBuffer buf;
if ( !g_pFullFileSystem->ReadFile( pFilename, "GAME", buf ) )
{
Msg( "SetupWave: File '%s' not found\n", pFilename );
return false;
}
// only supporting xwv format
xwvHeader_t* pHeader = (xwvHeader_t *)buf.Base();
if ( pHeader->id != XWV_ID || pHeader->version != XWV_VERSION || pHeader->format != XWV_FORMAT_XMA )
{
Msg( "SetupWave: File '%s' has bad format\n", pFilename );
return false;
}
XAUDIOSOURCEVOICEINIT SourceVoiceInit = { 0 };
SourceVoiceInit.Format.SampleType = XAUDIOSAMPLETYPE_XMA;
SourceVoiceInit.Format.NumStreams = 1;
SourceVoiceInit.MaxPacketCount = 1;
SourceVoiceInit.Format.Stream[0].SampleRate = pHeader->GetSampleRate();
SourceVoiceInit.Format.Stream[0].ChannelCount = pHeader->channels;
// create enough source voices to support simultaneous play of this sound
HRESULT hr;
numSimultaneous = min( numSimultaneous, ARRAYSIZE( m_pSourceVoices ) );
for ( int i=0; i<numSimultaneous; i++ )
{
// create the voice
hr = XAudioCreateSourceVoice( &SourceVoiceInit, &m_pSourceVoices[i] );
if ( FAILED( hr ) )
{
return false;
}
}
m_numVoices = numSimultaneous;
// get the xma data
m_nXMADataSize = pHeader->dataSize;
m_pXMAData = (unsigned char *)XPhysicalAlloc( m_nXMADataSize, MAXULONG_PTR, 0, PAGE_READWRITE );
V_memcpy( m_pXMAData, (unsigned char *)buf.Base() + pHeader->dataOffset, m_nXMADataSize );
return true;
}
bool IsPlaying()
{
XAUDIOSOURCESTATE SourceState;
for ( int i=0; i<m_numVoices; i++ )
{
m_pSourceVoices[i]->GetVoiceState( &SourceState );
if ( SourceState & XAUDIOSOURCESTATE_STARTED )
{
return true;
}
}
return false;
}
void Play()
{
int numPlaying = 0;
XAUDIOSOURCESTATE SourceState;
for ( int i=0; i<m_numVoices; i++ )
{
m_pSourceVoices[i]->GetVoiceState( &SourceState );
if ( SourceState & XAUDIOSOURCESTATE_STARTED )
{
numPlaying++;
}
}
if ( numPlaying >= m_numVoices )
{
return;
}
// find a free voice
IXAudioSourceVoice *pVoice = NULL;
for ( int i=0; i<m_numVoices; i++ )
{
m_pSourceVoices[i]->GetVoiceState( &SourceState );
if ( !( SourceState & XAUDIOSOURCESTATE_STARTED ) )
{
// use the free voice
pVoice = m_pSourceVoices[i];
break;
}
}
if ( !pVoice )
{
// none free
return;
}
// Set up packet
XAUDIOPACKET Packet = { 0 };
Packet.pBuffer = m_pXMAData;
Packet.BufferSize = m_nXMADataSize;
// Submit packet
HRESULT hr = pVoice->SubmitPacket( &Packet, XAUDIOSUBMITPACKET_DISCONTINUITY );
if ( !FAILED( hr ) )
{
pVoice->Start( 0 );
}
}
void Stop()
{
for ( int i=0; i<m_numVoices; i++ )
{
m_pSourceVoices[i]->Stop( 0 );
}
}
void Release()
{
for ( int i=0; i<ARRAYSIZE( m_pSourceVoices ); i++ )
{
if ( m_pSourceVoices[i] )
{
m_pSourceVoices[i]->Release();
m_pSourceVoices[i] = NULL;
}
}
if ( m_pXMAData )
{
XPhysicalFree( m_pXMAData );
m_pXMAData = NULL;
}
m_nXMADataSize = 0;
m_numVoices = 0;
}
private:
IXAudioSourceVoice *m_pSourceVoices[4];
unsigned char *m_pXMAData;
int m_nXMADataSize;
int m_numVoices;
};
//-----------------------------------------------------------------------------
// The application object
//-----------------------------------------------------------------------------
class CAppChooser : public CVguiSteamApp
{
typedef CVguiSteamApp BaseClass;
public:
virtual bool Create();
virtual bool PreInit();
virtual int Main();
virtual void Destroy();
void OnSelectionPrevious();
void OnSelectionNext();
void OnActivateGame();
void OnInactivityTimeout();
void OnStopDemoMovie();
void OnDemoMovieEnd();
void OnStopStartupMovie();
void Reset();
void ResetTimeout( float timeout );
bool IsInputEnabled() { return m_bInputEnabled; }
bool IsDemoMoviePlaying() { return m_bPlayingDemoMovie; }
bool IsStartupMoviePlaying() { return m_bPlayingStartupMovies; }
void HandleInvite( DWORD nUserId );
private:
const char *GetAppName() { return "AppChooser"; }
bool CreateWindow( int width, int height, bool fullscreen );
bool InitMaterialSystem();
bool InitVGUI();
void ShutdownVGUI();
bool InitAudio();
void ShutdownAudio();
void FrameTick();
void RenderScene();
void ExitChooser();
void EnableInput( bool bEnable );
void StartExitingProcess();
void SetLoadingIconPosition( bool bIsMultiplayer );
int m_nScreenWidth;
int m_nScreenHeight;
HWND m_hWnd;
float m_FadeInTime;
float m_FadeOutTime;
float m_StartTime;
float m_Timeout;
int m_Selection;
int m_LastSelection;
bool m_bInputEnabled;
int m_ExitingFrameCount;
float m_GameMovieStartTime;
float m_BackgroundMovieStartTime;
int m_LastBackgroundMovie;
int m_StartupMovie;
// various operating states
bool m_bPlayingStartupMovies;
bool m_bPlayingDemoMovie;
bool m_bExiting;
// Live invite handling
bool m_bInviteAccepted;
XNKID m_InviteSessionID;
DWORD m_nInviteUserID;
int m_iImageID;
vgui::Panel *m_pRootPanel;
CImage *m_pPersistedImage;
CImage *m_pOverlay;
CImage *m_pFullBlack;
CMovieImage *m_pStartupMovieImage;
CMovieImage *m_pBackgroundMovie;
CMovieImage *m_pDemoMovieImage;
CGamePanel *m_pGames[ARRAYSIZE( g_Games )];
CImage *m_pProductTitle;
CShadowLabel *m_pInstructionsShadow;
vgui::Label *m_pInstructions;
vgui::Label *m_pLoading;
CImage *m_pLoadingIcon;
CImage *m_pProductBackgrounds[ARRAYSIZE( g_Games )];
DWORD m_iStorageDeviceID;
DWORD m_iUserIdx;
KeyValues *m_pKVSettings;
int m_DemoMovie;
CXSound m_Click;
CXSound m_Clack;
CXSound m_Deny;
};
CAppChooser g_AppChooserSystem;
//--------------------------------------------------------------------------------------
// InitMaterialSystem
//
//--------------------------------------------------------------------------------------
bool CAppChooser::InitMaterialSystem()
{
RECT rect;
MaterialSystem_Config_t config;
config.SetFlag( MATSYS_VIDCFG_FLAGS_WINDOWED, IsPC() ? true : false );
config.SetFlag( MATSYS_VIDCFG_FLAGS_NO_WAIT_FOR_VSYNC, 0 );
config.m_VideoMode.m_Width = 0;
config.m_VideoMode.m_Height = 0;
config.m_VideoMode.m_Format = IMAGE_FORMAT_BGRX8888;
config.m_VideoMode.m_RefreshRate = 0;
config.dxSupportLevel = IsX360() ? 98 : 0;
g_pMaterialSystem->ModInit();
bool modeSet = g_pMaterialSystem->SetMode( m_hWnd, config );
if ( !modeSet )
{
Error( "Failed to set mode\n" );
return false;
}
g_pMaterialSystem->OverrideConfig( config, false );
GetClientRect( m_hWnd, &rect );
m_nScreenWidth = rect.right;
m_nScreenHeight = rect.bottom;
return true;
}
void CAppChooser::SetLoadingIconPosition( bool bIsMultiplayer )
{
// matches config from matsys_interface.cpp::InitStartupScreen()
float flNormalizedX;
float flNormalizedY;
float flNormalizedSize;
if ( !bIsMultiplayer )
{
flNormalizedX = 0.5f;
flNormalizedY = 0.86f;
flNormalizedSize = 0.1f;
}
else
{
flNormalizedX = 0.5f;
flNormalizedY = 0.9f;
flNormalizedSize = 0.1f;
}
// matches calcs from CShaderDeviceDx8::RefreshFrontBufferNonInteractive()
float flXPos = flNormalizedX;
float flYPos = flNormalizedY;
float flHeight = flNormalizedSize;
int nSize = m_nScreenHeight * flHeight;
int x = m_nScreenWidth * flXPos - nSize * 0.5f;
int y = m_nScreenHeight * flYPos - nSize * 0.5f;
int w = nSize;
int h = nSize;
m_pLoadingIcon->SetBounds( x, y, w, h );
}
//-----------------------------------------------------------------------------
// Setup all our VGUI info
//-----------------------------------------------------------------------------
bool CAppChooser::InitVGUI( void )
{
int x, y, w, h, g;
KeyValues *pKV;
vgui::surface()->GetScreenSize( w, h );
float aspectRatio = (float)w/(float)h;
bool bIsWidescreen = ( aspectRatio >= 1.7f );
bool bIsHiDef = h > 480;
const char *pResolutionKey = "";
if ( bIsWidescreen )
{
// 16:9 aspect
if ( bIsHiDef )
{
pResolutionKey = "_hidef";
}
else
{
pResolutionKey = "_lodef_wide";
}
}
else
{
// 4:3 apsect
if ( bIsHiDef )
{
pResolutionKey = "_hidef_norm";
}
else
{
pResolutionKey = "_lodef";
}
}
// Start vgui
vgui::ivgui()->Start();
vgui::ivgui()->SetSleep( false );
// load the scheme
vgui::scheme()->LoadSchemeFromFile( "resource/sourcescheme.res", NULL );
vgui::HScheme hScheme = vgui::scheme()->GetScheme( "SourceScheme" );
vgui::IScheme *pSourceScheme = vgui::scheme()->GetIScheme( hScheme );
m_pKVSettings = new KeyValues( "AppChooser.res" );
if ( m_pKVSettings->LoadFromFile( g_pFullFileSystem, "resource/UI/AppChooser.res", "GAME" ) )
{
m_pKVSettings->ProcessResolutionKeys( pResolutionKey );
}
else
{
return false;
}
// localization
g_pVGuiLocalize->AddFile( "resource/gameui_%language%.txt" ,"GAME", true );
// Init the root panel
m_pRootPanel = new vgui::Panel( NULL, "RootPanel" );
m_pRootPanel->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight );
m_pRootPanel->SetPaintBackgroundEnabled( true );
m_pRootPanel->SetVisible( true );
vgui::surface()->SetEmbeddedPanel( m_pRootPanel->GetVPanel() );
// need a full pure opaque black before all panel drawing
// this fixes the top of frame xmv video render work
m_pFullBlack = new CImage( m_pRootPanel, "FullBlack", "vgui/black" );
SETUP_PANEL( m_pFullBlack );
m_pFullBlack->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight );
m_pFullBlack->SetVisible( true );
// all the background movies were authored for widescreen
m_pBackgroundMovie = new CMovieImage( m_pRootPanel, "Movie", "Background", m_nScreenWidth, m_nScreenHeight, 0.0f );
SETUP_PANEL( m_pBackgroundMovie );
m_pBackgroundMovie->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight );
m_pBackgroundMovie->SetAspectRatio( bIsWidescreen ? 1.0f : 1.778f, false );
m_pBackgroundMovie->SetColor( 255, 255, 255 );
m_pBackgroundMovie->SetVisible( true );
pKV = m_pKVSettings->FindKey( "Logo" );
y = pKV->GetInt( "ypos" );
w = pKV->GetInt( "wide" );
h = pKV->GetInt( "tall" );
m_pProductTitle = new CImage( m_pRootPanel, "Logo", "vgui/appchooser/orangeboxlogo" );
SETUP_PANEL( m_pProductTitle );
m_pProductTitle->SetBounds( ( m_nScreenWidth - w )/2, y, w, h );
m_pProductTitle->SetVisible( true );
wchar_t *pString = g_pVGuiLocalize->Find( "#GameUI_AppChooser_SelectGame" );
pKV = m_pKVSettings->FindKey( "SelectGame" );
y = pKV->GetInt( "ypos" );
m_pInstructionsShadow = new CShadowLabel( m_pRootPanel, "SelectGame", pString );
SETUP_PANEL( m_pInstructionsShadow );
m_pInstructionsShadow->SetFont( pSourceScheme->GetFont( "ChapterTitleBlur" ) );
m_pInstructionsShadow->SizeToContents();
m_pInstructionsShadow->SetWide( m_nScreenWidth );
m_pInstructionsShadow->SetPos( 0, y );
m_pInstructionsShadow->SetContentAlignment( vgui::Label::a_center );
m_pInstructionsShadow->SetPaintBackgroundEnabled( false );
m_pInstructionsShadow->SetFgColor( Color( 0, 0, 0, 255) );
m_pInstructions = new vgui::Label( m_pRootPanel, "SelectGame", pString );
SETUP_PANEL( m_pInstructions );
m_pInstructions->SetFont( pSourceScheme->GetFont( "ChapterTitle" ) );
m_pInstructions->SizeToContents();
m_pInstructions->SetWide( m_nScreenWidth );
m_pInstructions->SetPos( 0, y );
m_pInstructions->SetContentAlignment( vgui::Label::a_center );
m_pInstructions->SetPaintBackgroundEnabled( false );
m_pInstructions->SetFgColor( Color( 255, 255, 255, 255) );
pKV = m_pKVSettings->FindKey( "GamePanel" );
w = pKV->GetInt( "wide" );
g = pKV->GetInt( "gap" );
x = ( m_nScreenWidth - ( (int)( ARRAYSIZE( g_Games ) ) * ( w + g ) - g ) ) / 2;
y = pKV->GetInt( "ypos" );
for ( int i=0; i<ARRAYSIZE( g_Games ); i++ )
{
m_pGames[i] = new CGamePanel( m_pRootPanel, "GamePanel", &g_Games[i], bIsWidescreen, m_pKVSettings );
SETUP_PANEL( m_pGames[i] );
m_pGames[i]->SetPos( x, y );
m_pGames[i]->SetPaintBackgroundType( 2 );
m_pGames[i]->SetSelected( false );
x += w + g;
}
// the product backgrounds are topmost and used as fade out materials
for ( int i=0; i<ARRAYSIZE( g_Games ); i++ )
{
char filename[MAX_PATH];
V_snprintf( filename, sizeof( filename ), "vgui/appchooser/background_%s%s", g_Games[i].pGameDir, bIsWidescreen ? "_widescreen" : "" );
m_pProductBackgrounds[i] = new CImage( m_pRootPanel, "Products", filename );
SETUP_PANEL( m_pProductBackgrounds[i] );
m_pProductBackgrounds[i]->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight );
m_pProductBackgrounds[i]->SetVisible( true );
m_pProductBackgrounds[i]->SetAlpha( 0 );
}
pString = g_pVGuiLocalize->Find( "#GameUI_Loading" );
y = m_nScreenHeight / 2;
m_pLoading = new vgui::Label( m_pRootPanel, "Text", pString );
SETUP_PANEL( m_pLoading );
m_pLoading->SetFont( pSourceScheme->GetFont( "ChapterTitle" ) );
m_pLoading->SizeToContents();
m_pLoading->SetWide( m_nScreenWidth );
m_pLoading->SetPos( 0, y );
m_pLoading->SetContentAlignment( vgui::Label::a_center );
m_pLoading->SetPaintBackgroundEnabled( false );
m_pLoading->SetFgColor( Color( 255, 255, 255, 255) );
m_pLoading->SetVisible( false );
m_pLoadingIcon = new CImage( m_pRootPanel, "LoadingIcon", "vgui/appchooser/loading_icon" );
SETUP_PANEL( m_pLoadingIcon );
SetLoadingIconPosition( false );
m_pLoadingIcon->SetVisible( false );
m_pLoadingIcon->SetAlpha( 0 );
// create back buffer cloned texture
ITexture *pTexture = NULL;
if ( XboxLaunch()->GetLaunchFlags() & LF_INTERNALLAUNCH )
{
pTexture = g_pMaterialSystem->CreateProceduralTexture(
"PersistedTexture",
TEXTURE_GROUP_OTHER,
m_nScreenWidth,
m_nScreenHeight,
g_pMaterialSystem->GetBackBufferFormat(),
TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY );
// the persisted texture is in the back buffer, get it
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
pRenderContext->CopyRenderTargetToTexture( pTexture );
}
// create a material to bind the persisted texture
// the fade-in material is topmost, fully opaque, and fades to transluscent on initial rendering
KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
pVMTKeyValues->SetInt( "$vertexcolor", 1 );
pVMTKeyValues->SetInt( "$vertexalpha", 1 );
pVMTKeyValues->SetInt( "$linearwrite", 1 ); // These 2 lines are needed so that we don't lose bits of precision in
pVMTKeyValues->SetInt( "$gammacolorread", 1 ); // the dark colors due to the 360's lossy sRGB read hardware
pVMTKeyValues->SetInt( "$ignorez", 1 );
if ( pTexture )
{
pVMTKeyValues->SetString( "$basetexture", "PersistedTexture" );
}
else
{
// there is no persisted texture, fade in from black
pVMTKeyValues->SetString( "$basetexture", "vgui/black" );
}
IMaterial *pFadeInMaterial = g_pMaterialSystem->CreateMaterial( "__FadeInMaterial.vmt", pVMTKeyValues );
// the persisted image is either the image from the relaunch or black during first boot
m_pPersistedImage = new CImage( m_pRootPanel, "FadeInOverlay", pFadeInMaterial );
SETUP_PANEL( m_pPersistedImage );
m_pPersistedImage->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight );
m_pPersistedImage->SetVisible( true );
m_pPersistedImage->SetAlpha( 255 );
m_pOverlay = m_pPersistedImage;
// full screen demo movie, use letterboxing
m_pDemoMovieImage = new CMovieImage( m_pRootPanel, "Movie", "Demo", m_nScreenWidth, m_nScreenHeight, 2.0f );
SETUP_PANEL( m_pDemoMovieImage );
m_pDemoMovieImage->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight );
m_pDemoMovieImage->SetAspectRatio( bIsWidescreen ? 1.0f : 1.778f, true );
m_pDemoMovieImage->SetVisible( true );
// full screen startup movies, use letterboxing, instant start
m_pStartupMovieImage = new CMovieImage( m_pRootPanel, "Movie", "Startup", m_nScreenWidth, m_nScreenHeight, 0.0f );
SETUP_PANEL( m_pStartupMovieImage );
m_pStartupMovieImage->SetBounds( 0, 0, m_nScreenWidth, m_nScreenHeight );
m_pStartupMovieImage->SetAspectRatio( bIsWidescreen ? 1.0f : 1.778f, true );
m_pStartupMovieImage->SetVisible( true );
return true;
}
//-----------------------------------------------------------------------------
// Stop VGUI
//-----------------------------------------------------------------------------
void CAppChooser::ShutdownVGUI( void )
{
delete m_pRootPanel;
}
//-----------------------------------------------------------------------------
// Initialize Audio
//-----------------------------------------------------------------------------
bool CAppChooser::InitAudio()
{
// Set up initialize parameters of XAudio Engine
// Both threads on core 2
XAUDIOENGINEINIT EngineInit = { 0 };
EngineInit.pEffectTable = &XAudioDefaultEffectTable;
EngineInit.ThreadUsage = XAUDIOTHREADUSAGE_THREAD4 | XAUDIOTHREADUSAGE_THREAD5;
// Initialize the XAudio Engine
HRESULT hr = XAudioInitialize( &EngineInit );
if ( FAILED( hr ) )
{
Error( "Error calling XAudioInitialize\n" );
}
m_Click.SetupWave( "sound/ui/buttonclick.360.wav", 2 );
m_Clack.SetupWave( "sound/ui/buttonclickrelease.360.wav", 2 );
m_Deny.SetupWave( "sound/player/suit_denydevice.360.wav", 2 );
return true;
}
void CAppChooser::ShutdownAudio()
{
// Shut down and free XAudio resources
XAudioShutDown();
}
//--------------------------------------------------------------------------------------
// Reset timeout
//--------------------------------------------------------------------------------------
void CAppChooser::ResetTimeout( float timeout )
{
if ( timeout > 0 )
{
m_Timeout = Plat_FloatTime() + timeout;
}
else
{
m_Timeout = 0;
}
}
//--------------------------------------------------------------------------------------
// Intialization Reset. Expected to be called once after inits.
//--------------------------------------------------------------------------------------
void CAppChooser::Reset()
{
// set selection to previous game or default
m_Selection = 0;
m_LastSelection = 0;
const char *pGameName = CommandLine()->ParmValue( "-game", "ep2" );
for ( int i=0; i<ARRAYSIZE( g_Games ); i++ )
{
if ( V_stristr( pGameName, g_Games[i].pGameDir ) )
{
m_Selection = i;
break;
}
}
// get the storage device
m_iStorageDeviceID = XboxLaunch()->GetStorageID();
Msg( "Storage ID: %d", m_iStorageDeviceID );
// Set the user index
m_iUserIdx = XboxLaunch()->GetUserID();
if ( g_pInputSystem )
{
g_pInputSystem->SetPrimaryUserId( m_iUserIdx );
}
XBX_SetPrimaryUserId( m_iUserIdx );
Msg( "User ID: %d", m_iUserIdx );
m_StartTime = Plat_FloatTime();
m_FadeInTime = 0;
m_FadeOutTime = 0;
m_bExiting = false;
m_ExitingFrameCount = 0;
m_bPlayingDemoMovie = false;
m_DemoMovie = 0;
// background movie and selection startup in sync
m_LastBackgroundMovie = m_Selection;
m_BackgroundMovieStartTime = 0;
m_GameMovieStartTime = 0;
EnableInput( true );
if ( !( XboxLaunch()->GetLaunchFlags() & LF_INTERNALLAUNCH ) )
{
// first time boot
// startup movies play first, and never again
ResetTimeout( 0 );
m_StartupMovie = -1;
m_bPlayingStartupMovies = true;
}
else
{
// normal background startup
ResetTimeout( INACTIVITY_TIMEOUT );
// foreground movie starts a little staggered
m_BackgroundMovieStartTime = Plat_FloatTime();
m_GameMovieStartTime = m_BackgroundMovieStartTime + 1.0f;
}
// Init our invite data
m_bInviteAccepted = false;
m_nInviteUserID = XBX_INVALID_USER_ID;
memset( (void *)&m_InviteSessionID, 0, sizeof( m_InviteSessionID ) );
}
//--------------------------------------------------------------------------------------
// Handle previous selection
//--------------------------------------------------------------------------------------
void CAppChooser::OnSelectionPrevious()
{
// backward wraparound
m_Selection = ( m_Selection + ARRAYSIZE( g_Games ) - 1 ) % ARRAYSIZE( g_Games );
m_Click.Play();
}
//--------------------------------------------------------------------------------------
// Handle next selection
//--------------------------------------------------------------------------------------
void CAppChooser::OnSelectionNext()
{
// forward wraparound
m_Selection = ( m_Selection + ARRAYSIZE( g_Games ) + 1 ) % ARRAYSIZE( g_Games );
m_Click.Play();
}
void CAppChooser::EnableInput( bool bEnable )
{
m_bInputEnabled = bEnable;
}
//--------------------------------------------------------------------------------------
// Chooser exits
//--------------------------------------------------------------------------------------
void CAppChooser::ExitChooser()
{
// reset stale arguments that encode prior game
// launcher will establish correct arguments based on desired game
CommandLine()->RemoveParm( "-game" );
CommandLine()->AppendParm( "-game", g_Games[m_Selection].pGameDir );
// Special command line parameter for tf. Command line args persist across
// relaunches, so remove it first in case we came from tf and are going to another game.
CommandLine()->RemoveParm( "-swapcores" );
if ( !Q_stricmp( g_Games[m_Selection].pGameDir, "tf" ) )
{
CommandLine()->AppendParm( "-swapcores", NULL );
}
int fFlags = LF_EXITFROMCHOOSER;
// allocate the full payload
int nPayloadSize = XboxLaunch()->MaxPayloadSize();
byte *pPayload = (byte *)stackalloc( nPayloadSize );
V_memset( pPayload, 0, nPayloadSize );
// payload is at least the command line
// any user data needed must be placed AFTER the command line
const char *pCmdLine = CommandLine()->GetCmdLine();
int nCmdLineLength = (int)strlen( pCmdLine ) + 1;
V_memcpy( pPayload, pCmdLine, min( nPayloadSize, nCmdLineLength ) );
// add any other data here to payload, after the command line
// ...
XboxLaunch()->SetStorageID( m_iStorageDeviceID );
m_iUserIdx = XBX_GetPrimaryUserId();
if ( m_bInviteAccepted )
{
// A potentially different user was invited, so we need to connect them
m_iUserIdx = m_nInviteUserID;
}
XboxLaunch()->SetUserID( m_iUserIdx );
if ( m_bInviteAccepted )
{
// In the case of an invitation acceptance, we need to pack extra data into the payload
fFlags |= LF_INVITERESTART;
XboxLaunch()->SetInviteSessionID( &m_InviteSessionID );
}
// Save out the data
bool bLaunch = XboxLaunch()->SetLaunchData( (void *)pPayload, nPayloadSize, fFlags );
if ( bLaunch )
{
COM_TimestampedLog( "Launching: \"%s\" Flags: 0x%8.8x", pCmdLine, XboxLaunch()->GetLaunchFlags() );
g_pMaterialSystem->PersistDisplay();
ShutdownVGUI();
ShutdownAudio();
XBX_DisconnectConsoleMonitor();
XboxLaunch()->Launch();
}
}
//--------------------------------------------------------------------------------------
// Handle game selection
//--------------------------------------------------------------------------------------
void CAppChooser::OnActivateGame()
{
if ( !m_FadeInTime || Plat_FloatTime() <= m_FadeInTime + 1.0f )
{
// lockout user selection input until screen has stable rendering and completed its fade in
// prevents button mashing doing an immediate game selection just as the startup movies end
return;
}
if ( !g_Games[m_Selection].bEnabled )
{
m_Deny.Play();
return;
}
m_Clack.Play();
while ( m_Clack.IsPlaying() )
{
// let the audio complete
Sleep( 1 );
}
StartExitingProcess();
}
void CAppChooser::OnDemoMovieEnd()
{
g_AppChooserSystem.ResetTimeout( INACTIVITY_TIMEOUT );
m_bPlayingDemoMovie = false;
}
void DemoMovieCallback()
{
g_AppChooserSystem.OnDemoMovieEnd();
}
//--------------------------------------------------------------------------------------
// Handle inactivity event
//--------------------------------------------------------------------------------------
void CAppChooser::OnInactivityTimeout()
{
// no further inactivity timeouts
ResetTimeout( 0 );
const char *pDemoMovieName = g_DemoMovies[m_DemoMovie];
m_DemoMovie++;
if ( m_DemoMovie >= ARRAYSIZE( g_DemoMovies ) )
{
// reset
m_DemoMovie = 0;
}
m_bPlayingDemoMovie = m_pDemoMovieImage->StartMovieFromFile( pDemoMovieName, true, false, DemoMovieCallback );
if ( !m_bPlayingDemoMovie )
{
// try again, later
ResetTimeout( INACTIVITY_TIMEOUT );
}
}
//--------------------------------------------------------------------------------------
// Handle demo stop request
//--------------------------------------------------------------------------------------
void CAppChooser::OnStopDemoMovie()
{
m_pDemoMovieImage->StopMovie();
}
//--------------------------------------------------------------------------------------
// Handle startup stop request
//--------------------------------------------------------------------------------------
void CAppChooser::OnStopStartupMovie()
{
if ( m_StartupMovie >= 0 && g_StartupMovies[m_StartupMovie].bUserCanSkip )
{
m_pStartupMovieImage->StopMovie();
}
}
//--------------------------------------------------------------------------------------
// Setup the exiting context. Cannot be aborted.
//--------------------------------------------------------------------------------------
void CAppChooser::StartExitingProcess()
{
bool bIsMultiplayer = V_stricmp( g_Games[m_Selection].pGameDir, "tf" ) == 0;
// stable rendering, start the fade out
m_pOverlay = m_pProductBackgrounds[m_Selection];
m_pLoading->SetVisible( true );
m_pLoading->SetAlpha( 0 );
SetLoadingIconPosition( bIsMultiplayer );
m_pLoadingIcon->SetVisible( true );
m_pLoadingIcon->SetAlpha( 0 );
m_FadeOutTime = Plat_FloatTime();
m_bExiting = true;
m_ExitingFrameCount = 0;
// ensure that nothing can stop the exiting process
// otherwise the player could rapidly select a game during the fade outs
ResetTimeout( 0 );
EnableInput( false );
}
//--------------------------------------------------------------------------------------
// Handle per frame update, prior to render
//--------------------------------------------------------------------------------------
void CAppChooser::FrameTick()
{
if ( m_ExitingFrameCount )
{
return;
}
g_TargetMovieVolume = 1.0f;
BOOL bControl;
if ( ERROR_SUCCESS == XMPTitleHasPlaybackControl(&bControl) )
{
g_TargetMovieVolume = bControl ? 1.0f : 0.0f;
}
if ( m_FadeInTime )
{
// fade in overlay goes from [1..0] and holds on 0
float t = ( Plat_FloatTime() - m_FadeInTime ) * 2.0f;
t = 1.0f - clamp( t, 0.0f, 1.0f );
m_pOverlay->SetAlpha ( t * 255.0f );
}
if ( m_FadeOutTime )
{
// fade out overlay goes from [0..1] and holds on 1
float t = ( Plat_FloatTime() - m_FadeOutTime ) * 2.0f;
t = clamp( t, 0.0f, 1.0f );
m_pOverlay->SetAlpha ( t * 255.0f );
}
for ( int i=0; i<ARRAYSIZE( g_Games ); i++ )
{
m_pGames[i]->SetSelected( false );
}
m_pGames[m_Selection]->SetSelected( true );
if ( m_bExiting )
{
m_pLoading->SetAlpha( m_pOverlay->GetAlpha() );
m_pLoadingIcon->SetAlpha( m_pOverlay->GetAlpha() );
if ( m_pOverlay->GetAlpha() == 255 )
{
// exiting needs to have fade overlay fully opaque before stopping background movie
m_pBackgroundMovie->StopMovie();
// exiting commences after all movies full release and enough frames have swapped to ensure stability
// strict time (frame rate) cannot be trusted, must allow a non trivial amount of presents
m_ExitingFrameCount = 30;
}
}
if ( m_bPlayingStartupMovies )
{
if ( m_pStartupMovieImage->IsFullyReleased() )
{
m_StartupMovie++;
if ( m_StartupMovie >= ARRAYSIZE( g_StartupMovies ) )
{
// end of cycle
m_StartupMovie = -1;
m_bPlayingStartupMovies = false;
}
else
{
m_bPlayingStartupMovies = m_pStartupMovieImage->StartMovieFromFile( g_StartupMovies[m_StartupMovie].pMovieName, true, false );
}
}
if ( !m_bPlayingStartupMovies )
{
ResetTimeout( INACTIVITY_TIMEOUT );
m_BackgroundMovieStartTime = Plat_FloatTime();
m_GameMovieStartTime = m_BackgroundMovieStartTime + 1.0f;
}
else
{
return;
}
}
if ( m_bInviteAccepted )
{
// stop attract mode
if ( m_bPlayingDemoMovie )
{
// hint the demo movie to stop
OnStopDemoMovie();
}
// attract movies must finish before allowing invite
// background movies must finish before allowing invite
// starts the exiting process ONCE
if ( !m_bPlayingStartupMovies && !m_bPlayingDemoMovie && !m_bExiting )
{
for ( int i = 0; i < ARRAYSIZE( g_Games ); i++ )
{
if ( V_stristr( "tf", g_Games[i].pGameDir ) )
{
// EVIL! spoof a user slection to invite only TF
m_Selection = i;
StartExitingProcess();
// must fixup state, invite could have happened very early, at any time, before the initial fade-in
// ensure no movies start
m_BackgroundMovieStartTime = 0;
m_GameMovieStartTime = 0;
// hack the fade times to match the expected state
if ( !m_FadeInTime || m_FadeInTime >= Plat_FloatTime() )
{
// can't fade out, invite occured before fade-in
// can only snap it because there is no background to fade from
m_pPersistedImage->SetAlpha( 0 );
m_FadeInTime = 0;
m_FadeOutTime = 0;
m_pOverlay->SetAlpha( 255 );
}
break;
}
}
}
}
if ( m_bExiting || m_bPlayingDemoMovie || m_LastSelection != m_Selection )
{
m_pGames[m_LastSelection]->StopMovie();
m_LastSelection = m_Selection;
// user can change selection very quickly
// lag the movie starting until selection settles
m_GameMovieStartTime = Plat_FloatTime() + 2.0f;
}
if ( !m_bExiting && !m_bPlayingDemoMovie && ( m_GameMovieStartTime != 0 ) && ( Plat_FloatTime() >= m_GameMovieStartTime ) )
{
// keep trying the current selection, it will eventually start
m_pGames[m_Selection]->StartMovie();
if ( m_LastBackgroundMovie != m_Selection )
{
m_pBackgroundMovie->StopMovie();
m_LastBackgroundMovie = m_Selection;
m_BackgroundMovieStartTime = Plat_FloatTime() + 2.0f;
}
}
// update the background movie, lags far behind any user selection
if ( !m_bExiting && !m_bPlayingDemoMovie && ( m_BackgroundMovieStartTime != 0 ) && ( Plat_FloatTime() >= m_BackgroundMovieStartTime ) )
{
char szFilename[MAX_PATH];
V_snprintf( szFilename, sizeof( szFilename ), "background_%s.wmv", g_Games[m_Selection].pGameDir );
// keep trying the current selection, it will eventually start
bool bStarted = m_pBackgroundMovie->StartMovieFromFile(
szFilename,
false,
true );
if ( bStarted )
{
m_LastBackgroundMovie = m_Selection;
}
}
}
//--------------------------------------------------------------------------------------
// Render the scene
//--------------------------------------------------------------------------------------
void CAppChooser::RenderScene()
{
FrameTick();
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
g_pMaterialSystem->BeginFrame( Plat_FloatTime() );
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Flush( true );
// render all movies, XMV needs to hijack D3D
bool bPreviousState = g_pMaterialSystem->OwnGPUResources( false );
{
m_pStartupMovieImage->RenderVideoFrame();
m_pBackgroundMovie->RenderVideoFrame();
m_pDemoMovieImage->RenderVideoFrame();
for ( int i=0; i<ARRAYSIZE( m_pGames ); i++ )
{
m_pGames[i]->m_pMovieImage->RenderVideoFrame();
}
}
g_pMaterialSystem->OwnGPUResources( bPreviousState );
pRenderContext->Viewport( 0, 0, m_nScreenWidth, m_nScreenHeight );
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->LoadIdentity();
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->LoadIdentity();
pRenderContext->MatrixMode( MATERIAL_MODEL );
pRenderContext->LoadIdentity();
pRenderContext->Flush( true );
vgui::ivgui()->RunFrame();
vgui::surface()->PaintTraverseEx( vgui::surface()->GetEmbeddedPanel() );
g_pMaterialSystem->EndFrame();
g_pMaterialSystem->SwapBuffers();
if ( !m_FadeInTime && !m_bPlayingStartupMovies && !m_bExiting )
{
// stable rendering, start the fade in
m_FadeInTime = Plat_FloatTime() + 1.0f;
}
// check for timeout
if ( m_Timeout && ( Plat_FloatTime() >= m_Timeout ) )
{
m_Timeout = 0;
OnInactivityTimeout();
}
if ( m_ExitingFrameCount > 1 && m_pBackgroundMovie->IsFullyReleased() && m_pGames[m_Selection]->m_pMovieImage->IsFullyReleased() )
{
// must wait for a few frames to settle to ensure frame buffer is stable
m_ExitingFrameCount--;
if ( m_ExitingFrameCount == 1 )
{
ExitChooser();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAppChooser::HandleInvite( DWORD nUserId )
{
// invites are asynchronous and could happen at any time
if ( m_bInviteAccepted )
{
// already accepted
return;
}
// Collect our session data
XINVITE_INFO inviteInfo;
DWORD dwError = XInviteGetAcceptedInfo( nUserId, &inviteInfo );
if ( dwError != ERROR_SUCCESS )
{
return;
}
// We only care if we're asked to join an Orange Box session
if ( inviteInfo.dwTitleID != TITLEID_THE_ORANGE_BOX )
{
return;
}
// Store off the session ID and mark the invite as accepted internally
m_bInviteAccepted = true;
m_InviteSessionID = inviteInfo.hostInfo.sessionID;
m_nInviteUserID = nUserId;
// must wait for the startup movies or attract mode to end
// FrameTick() will detect the invite and transition
// the user has accpeted and cannot abort the invite
EnableInput( false );
ResetTimeout( 0 );
}
//--------------------------------------------------------------------------------------
// Window Proc
//--------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam )
{
switch ( iMsg )
{
case WM_CLOSE:
g_bActive = false;
break;
case WM_DESTROY:
PostQuitMessage( 0 );
return 0L;
case WM_LIVE_INVITE_ACCEPTED:
g_AppChooserSystem.HandleInvite( LOWORD( lParam ) );
break;
case WM_XCONTROLLER_KEY:
if ( !g_AppChooserSystem.IsInputEnabled() )
{
break;
}
if ( g_AppChooserSystem.IsStartupMoviePlaying() )
{
// some startup movies can be aborted
switch ( wParam )
{
case KEY_XBUTTON_A:
case KEY_XBUTTON_START:
if ( LOWORD( lParam ) )
{
g_AppChooserSystem.OnStopStartupMovie();
}
}
// no other input is allowed during this state
break;
}
if ( g_AppChooserSystem.IsDemoMoviePlaying() )
{
// demo movies can be aborted
switch ( wParam )
{
case KEY_XBUTTON_A:
case KEY_XBUTTON_START:
if ( LOWORD( lParam ) )
{
g_AppChooserSystem.OnStopDemoMovie();
}
}
}
else
{
if ( LOWORD( lParam ) )
{
g_AppChooserSystem.ResetTimeout( INACTIVITY_TIMEOUT );
}
switch ( wParam )
{
case KEY_XBUTTON_LEFT:
case KEY_XSTICK1_LEFT:
if ( LOWORD( lParam ) )
{
g_AppChooserSystem.OnSelectionPrevious();
}
break;
case KEY_XBUTTON_RIGHT:
case KEY_XSTICK1_RIGHT:
if ( LOWORD( lParam ) )
{
g_AppChooserSystem.OnSelectionNext();
}
break;
case KEY_XBUTTON_A:
case KEY_XBUTTON_START:
if ( LOWORD( lParam ) )
{
g_AppChooserSystem.OnActivateGame();
}
break;
}
}
break;
}
return DefWindowProc( hWnd, iMsg, wParam, lParam );
}
//--------------------------------------------------------------------------------------
// CreateWindow
//
//--------------------------------------------------------------------------------------
bool CAppChooser::CreateWindow( int width, int height, bool bFullScreen )
{
HWND hWnd;
WNDCLASSEX wndClass;
DWORD dwStyle, dwExStyle;
int x, y, sx, sy;
if ( ( hWnd = FindWindow( GetAppName(), GetAppName() ) ) != NULL )
{
SetForegroundWindow( hWnd );
return true;
}
wndClass.cbSize = sizeof( wndClass );
wndClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wndClass.lpfnWndProc = ::WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = (HINSTANCE)GetAppInstance();
wndClass.hIcon = 0;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)COLOR_GRAYTEXT;
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = GetAppName();
wndClass.hIconSm = 0;
if ( !RegisterClassEx( &wndClass ) )
{
Error( "Window class registration failed\n" );
return false;
}
if ( bFullScreen )
{
dwExStyle = WS_EX_TOPMOST;
dwStyle = WS_POPUP | WS_VISIBLE;
}
else
{
dwExStyle = 0;
dwStyle = WS_CAPTION | WS_SYSMENU;
}
x = y = 0;
sx = width;
sy = height;
hWnd = CreateWindowEx(
dwExStyle,
GetAppName(), // window class name
GetAppName(), // window caption
dwStyle | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, // window style
x, // initial x position
y, // initial y position
sx, // initial x size
sy, // initial y size
NULL, // parent window handle
NULL, // window menu handle
(HINSTANCE)GetAppInstance(),// program instance handle
NULL); // creation parameter
if ( hWnd == NULL )
{
return false;
}
m_hWnd = hWnd;
return true;
}
//-----------------------------------------------------------------------------
// Create
//-----------------------------------------------------------------------------
bool CAppChooser::Create()
{
AppSystemInfo_t appSystems[] =
{
{ "filesystem_stdio.dll", QUEUEDLOADER_INTERFACE_VERSION },
{ "materialsystem.dll", MATERIAL_SYSTEM_INTERFACE_VERSION },
{ "inputsystem.dll", INPUTSYSTEM_INTERFACE_VERSION },
{ "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION },
{ "vguimatsurface.dll", VGUI_SURFACE_INTERFACE_VERSION },
{ "", "" }
};
MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f );
SpewOutputFunc( g_DefaultSpewFunc );
// Add in the cvar factory
AppModule_t cvarModule = LoadModule( VStdLib_GetICVarFactory() );
AddSystem( cvarModule, CVAR_INTERFACE_VERSION );
// vxconsole - true will block (legacy behavior)
XBX_InitConsoleMonitor( false );
if ( !AddSystems( appSystems ) )
return false;
IMaterialSystem* pMaterialSystem = (IMaterialSystem*)FindSystem( MATERIAL_SYSTEM_INTERFACE_VERSION );
if ( !pMaterialSystem )
{
Error( "Failed to find %s\n", MATERIAL_SYSTEM_INTERFACE_VERSION );
return false;
}
if ( IsX360() )
{
IFileSystem* pFileSystem = (IFileSystem*)FindSystem( FILESYSTEM_INTERFACE_VERSION );
if ( !pFileSystem )
{
Error( "Failed to find %s\n", FILESYSTEM_INTERFACE_VERSION );
return false;
}
pFileSystem->LoadModule( "shaderapidx9.dll" );
}
pMaterialSystem->SetShaderAPI( "shaderapidx9.dll" );
return true;
}
//-----------------------------------------------------------------------------
// PreInit
//-----------------------------------------------------------------------------
bool CAppChooser::PreInit()
{
if ( !BaseClass::PreInit() )
return false;
if ( !g_pFullFileSystem || !g_pMaterialSystem )
{
Warning( "Unable to find required interfaces!\n" );
return false;
}
// Add paths...
if ( !SetupSearchPaths( NULL, false, true ) )
{
Error( "Failed to setup search paths\n" );
return false;
}
// Create the main program window and our viewport
int w = 640;
int h = 480;
if ( IsX360() )
{
w = GetSystemMetrics( SM_CXSCREEN );
h = GetSystemMetrics( SM_CYSCREEN );
}
if ( !CreateWindow( w, h, false ) )
{
ChangeDisplaySettings( 0, 0 );
Error( "Unable to create main window\n" );
return false;
}
ShowWindow( m_hWnd, SW_SHOWNORMAL );
UpdateWindow( m_hWnd );
SetForegroundWindow( m_hWnd );
SetFocus( m_hWnd );
XOnlineStartup();
return true;
}
//-----------------------------------------------------------------------------
// Destroy
//-----------------------------------------------------------------------------
void CAppChooser::Destroy()
{
XOnlineCleanup();
}
//-----------------------------------------------------------------------------
// Main
//-----------------------------------------------------------------------------
int CAppChooser::Main()
{
if ( !InitMaterialSystem() )
{
return 0;
}
if ( !InitVGUI() )
{
return 0;
}
if ( !InitAudio() )
{
return 0;
}
// post initialization reset
Reset();
// Setup a listener for invites
XBX_NotifyCreateListener( XNOTIFY_LIVE );
MSG msg;
while ( g_bActive == TRUE )
{
// Pump the XBox notifications
XBX_ProcessEvents();
while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
g_pInputSystem->PollInputState();
RenderScene();
}
ShutdownVGUI();
ShutdownAudio();
Destroy();
return 0;
}
//-----------------------------------------------------------------------------
// The entry point for the application
//-----------------------------------------------------------------------------
extern "C" __declspec(dllexport) int AppChooserMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
CommandLine()->CreateCmdLine( lpCmdLine );
SetAppInstance( hInstance );
CSteamApplication steamApplication( &g_AppChooserSystem );
steamApplication.Run();
return 0;
}