|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//=======================================================================================//
#include "replaysystem.h"
#include "tier2/tier2.h"
#include "iserver.h"
#include "iclient.h"
#include "icliententitylist.h"
#include "igameevents.h"
#include "replay/ireplaymovierenderer.h"
#include "replay/ireplayscreenshotsystem.h"
#include "replay/replayutils.h"
#include "replay/replaylib.h"
#include "sv_sessionrecorder.h"
#include "sv_recordingsession.h"
#include "cl_screenshotmanager.h"
#include "netmessages.h"
#include "thinkmanager.h"
#include "managertest.h"
#include "vprof.h"
#include "sv_fileservercleanup.h"
#if !defined( _X360 )
#include "winlite.h"
#include "xbox/xboxstubs.h"
#endif
// TODO: Deal with linux build includes
#ifdef IS_WINDOWS_PC
#include <winsock.h>
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//----------------------------------------------------------------------------------------
#undef CreateEvent // Can't call IGameEventManager2::CreateEvent() without this
//----------------------------------------------------------------------------------------
#if !defined( DEDICATED )
IEngineClientReplay *g_pEngineClient = NULL; CClientReplayContext *g_pClientReplayContextInternal = NULL; IVDebugOverlay *g_pDebugOverlay = NULL; IDownloadSystem *g_pDownloadSystem = NULL; #endif
vgui::ILocalize *g_pVGuiLocalize = NULL; CServerReplayContext *g_pServerReplayContext = NULL; IClientReplay *g_pClient = NULL; IServerReplay *g_pServer = NULL; IEngineReplay *g_pEngine = NULL; IGameEventManager2 *g_pGameEventManager = NULL; IEngineTrace *g_pEngineTraceClient = NULL; IReplayDemoPlayer *g_pReplayDemoPlayer = NULL; IClientEntityList *entitylist = NULL; // icliententitylist.h forces the use of this name by externing in the header
//----------------------------------------------------------------------------------------
#define REPLAY_INIT( exp_ ) \
if ( !( exp_ ) ) \ { \ Warning( "CReplaySystem::Connect() failed on: \"%s\"!\n", #exp_ ); \ return false; \ }
//----------------------------------------------------------------------------------------
class CReplaySystem : public CTier2AppSystem< IReplaySystem > { typedef CTier2AppSystem< IReplaySystem > BaseClass;
public: virtual bool Connect( CreateInterfaceFn fnFactory ) { REPLAY_INIT( fnFactory ); REPLAY_INIT( BaseClass::Connect( fnFactory ) );
ConVar_Register( FCVAR_CLIENTDLL );
REPLAY_INIT( g_pFullFileSystem );
g_pEngine = (IEngineReplay *)fnFactory( ENGINE_REPLAY_INTERFACE_VERSION, NULL ); REPLAY_INIT( g_pEngine );
REPLAY_INIT( g_pEngine->IsSupportedModAndPlatform() ); #if !defined( DEDICATED )
g_pEngineClient = (IEngineClientReplay *)fnFactory( ENGINE_REPLAY_CLIENT_INTERFACE_VERSION, NULL ); REPLAY_INIT( g_pEngineClient );
g_pEngineTraceClient = (IEngineTrace *)fnFactory( INTERFACEVERSION_ENGINETRACE_CLIENT, NULL ); REPLAY_INIT( g_pEngineTraceClient );
g_pReplayDemoPlayer = (IReplayDemoPlayer *)fnFactory( INTERFACEVERSION_REPLAYDEMOPLAYER, NULL ); REPLAY_INIT( g_pReplayDemoPlayer );
g_pVGuiLocalize = (vgui::ILocalize *)fnFactory( VGUI_LOCALIZE_INTERFACE_VERSION, NULL ); REPLAY_INIT( g_pVGuiLocalize );
g_pDebugOverlay = ( IVDebugOverlay * )fnFactory( VDEBUG_OVERLAY_INTERFACE_VERSION, NULL ); REPLAY_INIT( g_pDebugOverlay ); #endif
g_pGameEventManager = (IGameEventManager2 *)fnFactory( INTERFACEVERSION_GAMEEVENTSMANAGER2, NULL ); REPLAY_INIT( g_pGameEventManager );
#if !defined( DEDICATED )
g_pDownloadSystem = (IDownloadSystem *)fnFactory( INTERFACEVERSION_DOWNLOADSYSTEM, NULL ); REPLAY_INIT( g_pDownloadSystem );
// Create client context now if not running a dedicated server
if ( !g_pEngine->IsDedicated() ) { g_pClientReplayContextInternal = new CClientReplayContext(); }
// ...and create server replay context if we are
else #endif
{ g_pServerReplayContext = new CServerReplayContext(); }
#if defined( DEDICATED )
REPLAY_INIT( ReplayLib_Init( g_pEngine->GetGameDir(), NULL ) ) // Init without the client replay context
#else
REPLAY_INIT( ReplayLib_Init( g_pEngine->GetGameDir(), g_pClientReplayContextInternal ) ); #endif
Test();
return true; }
virtual void Disconnect() { BaseClass::Disconnect(); }
virtual InitReturnVal_t Init() { InitReturnVal_t nRetVal = BaseClass::Init(); if ( nRetVal != INIT_OK ) return nRetVal;
return INIT_OK; }
virtual void Shutdown() { BaseClass::Shutdown();
#if !defined( DEDICATED )
delete g_pClientReplayContextInternal; g_pClientReplayContextInternal = NULL; #endif
delete g_pServerReplayContext; g_pServerReplayContext = NULL; }
virtual void Think() { VPROF_BUDGET( "CReplaySystem::Think", VPROF_BUDGETGROUP_REPLAY );
g_pThinkManager->Think(); }
virtual bool IsReplayEnabled() { extern ConVar replay_enable; return replay_enable.GetInt() != 0; }
virtual bool IsRecording() { // NOTE: demoplayer->IsPlayingBack() needs to be checked here, as "replay_enable" and
// "replay_recording" will inevitably get stored with signon data in any playing demo.
// If the !demoplayer->IsPlayingBack() line is omitted below, Replay_IsRecording()
// becomes useless during demo playback and will always return true.
extern ConVar replay_recording; #if !defined( DEDICATED )
return IsReplayEnabled() && replay_recording.GetInt() && !g_pEngineClient->IsDemoPlayingBack(); #else
return IsReplayEnabled() && replay_recording.GetInt(); #endif
}
//----------------------------------------------------------------------------------------
// Client-specific implementation:
//----------------------------------------------------------------------------------------
virtual bool CL_Init( CreateInterfaceFn fnClientFactory ) { #if !defined( DEDICATED )
g_pClient = (IClientReplay *)fnClientFactory( CLIENT_REPLAY_INTERFACE_VERSION, NULL ); if ( !g_pClient ) return false;
entitylist = (IClientEntityList *)fnClientFactory( VCLIENTENTITYLIST_INTERFACE_VERSION, NULL ); if ( !entitylist ) return false;
if ( !g_pClientReplayContextInternal->Init( fnClientFactory ) ) return false;
return true; #else
return false; #endif
}
virtual void CL_Shutdown() { #if !defined( DEDICATED )
if ( g_pClientReplayContextInternal && g_pClientReplayContextInternal->IsInitialized() ) { g_pClientReplayContextInternal->Shutdown(); } #endif
}
virtual void CL_Render() { #if !defined( DEDICATED )
// If the replay system wants to take a screenshot, do it now
if ( g_pClientReplayContextInternal->m_pScreenshotManager->ShouldCaptureScreenshot() ) { g_pClientReplayContextInternal->m_pScreenshotManager->DoCaptureScreenshot(); return; }
// Currently rendering? NOTE: GetMovieRenderer() only returns a valid ptr during rendering
IReplayMovieRenderer *pReplayMovieRenderer = g_pClientReplayContextInternal->GetMovieRenderer(); if ( !pReplayMovieRenderer ) return;
pReplayMovieRenderer->RenderVideo(); #endif
}
virtual IClientReplayContext *CL_GetContext() { #if !defined( DEDICATED )
return g_pClientReplayContextInternal; #else
return NULL; #endif
}
//----------------------------------------------------------------------------------------
// Server-specific implementation:
//----------------------------------------------------------------------------------------
virtual bool SV_Init( CreateInterfaceFn fnServerFactory ) { if ( !g_pEngine->IsDedicated() || !g_pServerReplayContext ) return false;
g_pServer = (IServerReplay *)fnServerFactory( SERVER_REPLAY_INTERFACE_VERSION, NULL ); if ( !g_pServer ) return false;
Assert( !ReplayServer() );
return g_pServerReplayContext->Init( fnServerFactory ); }
virtual void SV_Shutdown() { if ( g_pServerReplayContext && g_pServerReplayContext->IsInitialized() ) { g_pServerReplayContext->Shutdown(); } }
virtual IServerReplayContext *SV_GetContext() { return g_pServerReplayContext; }
virtual bool SV_ShouldBeginRecording( bool bIsInWaitingForPlayers ) { extern ConVar replay_enable;
return !bIsInWaitingForPlayers && #if !defined( DEDICATED )
!g_pEngineClient->IsPlayingReplayDemo() && #endif
replay_enable.GetBool(); }
virtual void SV_NotifyReplayRequested() { if ( !g_pEngine->IsSupportedModAndPlatform() ) return;
CServerRecordingSession *pSession = SV_GetRecordingSessionInProgress(); if ( !pSession ) return;
// A replay was requested - notify the session so we don't throw it away at the end of the round
pSession->NotifyReplayRequested(); }
virtual void SV_SendReplayEvent( const char *pEventName, int nClientSlot ) { // Attempt to create the event
IGameEvent *pEvent = g_pGameEventManager->CreateEvent( pEventName, true ); if ( !pEvent ) return;
SV_SendReplayEvent( pEvent, nClientSlot ); }
virtual void SV_SendReplayEvent( IGameEvent *pEvent, int nClientSlot/*=-1*/ ) { IServer *pGameServer = g_pEngine->GetGameServer();
if ( !pEvent ) return;
// Write event info to SVC_GameEvent msg
char buffer_data[MAX_EVENT_BYTES]; SVC_GameEvent msg; msg.SetReliable( false ); msg.m_DataOut.StartWriting( buffer_data, sizeof( buffer_data ) ); if ( !g_pGameEventManager->SerializeEvent( pEvent, &msg.m_DataOut ) ) { DevMsg( "Replay_SendReplayEvent(): failed to serialize event '%s'.\n", pEvent->GetName() ); goto free_event; }
// Send to all clients?
if ( nClientSlot == -1 ) { for ( int i = 0; i < pGameServer->GetClientCount(); ++i ) { IClient *pClient = pGameServer->GetClient( i ); if ( pClient ) { // Send the message
pClient->SendNetMsg( msg ); } } } else // Send to just the one client?
{ IClient *pClient = pGameServer->GetClient( nClientSlot ); if ( pClient ) { // Send the message
pClient->SendNetMsg( msg ); } }
free_event: g_pGameEventManager->FreeEvent( pEvent ); }
virtual void SV_EndRecordingSession( bool bForceSynchronousPublish/*=false*/ ) { if ( !g_pEngine->IsSupportedModAndPlatform() ) return;
if ( !ReplayServer() ) return;
SV_GetSessionRecorder()->StopRecording( false );
if ( bForceSynchronousPublish ) { // Publish all files
SV_GetSessionRecorder()->PublishAllSynchronous();
// This should unlock all sessions
SV_GetSessionRecorder()->UpdateSessionLocks();
// Let the session manager do any cleanup - this will remove files associated with a ditched
// session. For example, if the server was shut down in the middle of a round where no one
// saved a replay, the files will be published synchronously above and then cleaned up
// synchronously here.
SV_GetRecordingSessionManager()->DoSessionCleanup();
// Since the recording session manager will kick off a cleanup job, we need to wait for it
// here since we're shutting down.
SV_GetFileserverCleaner()->BlockForCompletion(); } }
void Test() { #if !defined( DEDICATED ) && _DEBUG
// This gets called after interfaces are hooked up, and before any of the
// internal replay systems get init'd.
CTestManager::Test(); #endif
} };
//----------------------------------------------------------------------------------------
static CReplaySystem s_Replay; IReplaySystem *g_pReplay = &s_Replay;
//----------------------------------------------------------------------------------------
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CReplaySystem, IReplaySystem, REPLAY_INTERFACE_VERSION, s_Replay );
//----------------------------------------------------------------------------------------
void Replay_MsgBox( const char *pText ) { g_pClient->DisplayReplayMessage( pText, false, true, NULL ); }
void Replay_MsgBox( const wchar_t *pText ) { g_pClient->DisplayReplayMessage( pText, false, true, NULL ); }
const char *Replay_GetDownloadURLPath() { static char s_szDownloadURLPath[MAX_OSPATH]; extern ConVar replay_fileserver_path; // NOTE: replicated
V_strcpy_safe( s_szDownloadURLPath, replay_fileserver_path.GetString() ); V_StripTrailingSlash( s_szDownloadURLPath ); V_FixSlashes( s_szDownloadURLPath, '/' );
// Get rid of starting slash
if ( s_szDownloadURLPath[0] == '/' ) return &s_szDownloadURLPath[1];
return s_szDownloadURLPath; }
const char *Replay_GetDownloadURL() { #if 0
// Get the local host name
char szHostname[MAX_OSPATH]; if ( gethostname( szHostname, sizeof( szHostname ) ) == -1 ) { Error( "Failed to send to Replay to client - couldn't get local IP.\n" ); return ""; } #endif
// Construct the URL based on replicated cvars
static char s_szFileURL[ MAX_OSPATH ]; extern ConVar replay_fileserver_protocol; extern ConVar replay_fileserver_host; extern ConVar replay_fileserver_port; V_snprintf( s_szFileURL, sizeof( s_szFileURL ), "%s://%s:%i/%s/", replay_fileserver_protocol.GetString(), replay_fileserver_host.GetString(), replay_fileserver_port.GetInt(), Replay_GetDownloadURLPath() );
// Cleanup
V_FixDoubleSlashes( s_szFileURL + V_strlen("http://") );
return s_szFileURL; }
//----------------------------------------------------------------------------------------
// Purpose: (client/server) Crack a URL into a base and a path
// NOTE: the URL *must contain a port* !
//
// Example: http://some.base.url:8080/a/path/here.txt cracks into:
// pBaseURL = "http://some.base.url:8080"
// pURLPath = "/a/path/here.txt"
//----------------------------------------------------------------------------------------
void Replay_CrackURL( const char *pURL, char *pBaseURLOut, char *pURLPathOut ) { const char *pColon; const char *pURLPath;
// Must at least have "http://"
if ( V_strlen(pURL) < 6 ) goto fail;
// Skip protocol ':' (eg http://)
pColon = V_strstr( pURL, ":" ); if ( !pColon ) goto fail;
// Find next colon
pColon = V_strstr( pColon + 1, ":" ); if ( !pColon ) goto fail;
// Copies "http[s]://<address>:<port>
pURLPath = V_strstr( pColon, "/" ); V_strncpy( pBaseURLOut, pURL, pURLPath - pURL + 1 ); V_strcpy( pURLPathOut, pURLPath );
return;
fail: AssertMsg( 0, "Replay_CrackURL() was passed an invalid URL and has failed. This should never happen." ); }
#ifndef DEDICATED
void Replay_HudMsg( const char *pText, const char *pSound, bool bUrgent ) { g_pClient->DisplayReplayMessage( pText, bUrgent, false, pSound ); } #endif
//----------------------------------------------------------------------------------------
|