diff options
Diffstat (limited to 'replay/replaysystem.cpp')
| -rw-r--r-- | replay/replaysystem.cpp | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/replay/replaysystem.cpp b/replay/replaysystem.cpp new file mode 100644 index 0000000..c268773 --- /dev/null +++ b/replay/replaysystem.cpp @@ -0,0 +1,522 @@ +//========= 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 + +//---------------------------------------------------------------------------------------- |