212 lines
7.5 KiB
C++
212 lines
7.5 KiB
C++
//========= Copyright (c) 1996-2009, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
//=======================================================================================//
|
|
|
|
#if defined( REPLAY_ENABLED )
|
|
#include "replay.h"
|
|
#include "convar.h"
|
|
#include "cmd.h"
|
|
#include "qlimits.h"
|
|
#include "client.h"
|
|
#include "server.h"
|
|
#include "enginesingleuserfilter.h"
|
|
#include "cdll_engine_int.h"
|
|
#include "filesystem.h"
|
|
#include "replayhistorymanager.h"
|
|
#include "toolframework/itoolframework.h"
|
|
#include "replayserver.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
CON_COMMAND_F( loadsfm, "Test -- Loads the SFM", 0 ) // TEST
|
|
{
|
|
toolframework->LoadFilmmaker();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
ConVar replay_enable( "replay_enable", "0", FCVAR_REPLICATED, "Enable Replay recording on server" );
|
|
ConVar replay_snapshotrate("replay_snapshotrate", "16", 0, "Snapshots broadcasted per second" );
|
|
|
|
static ConVar replay_autoretry( "replay_autoretry", "1", 0, "Relay proxies retry connection after network timeout" );
|
|
static ConVar replay_timeout( "replay_timeout", "30", 0, "SourceTV connection timeout in seconds." );
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
bool Replay_IsEnabled()
|
|
{
|
|
return replay_enable.GetInt() != 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
void Replay_OnFileSendComplete( const char *pFilename, int nSize )
|
|
{
|
|
if ( !replay || !replay->IsActive() )
|
|
{
|
|
AssertMsg( 0, "Calling Replay_OnFileSendComplete() with inactive Replay!" );
|
|
return;
|
|
}
|
|
|
|
// TODO - how can we get a client index here? Do we already have the state in each CNetChan
|
|
// and not need to duplicate it in a bitvec?
|
|
|
|
// Clear client's download bit
|
|
// replay->m_vecClientsDownloading.Set( nClientSlot, false );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
CON_COMMAND_F( request_replay_demo, "Request a replay demo from the server.", FCVAR_GAMEDLL )
|
|
{
|
|
if ( !Replay_IsEnabled() )
|
|
{
|
|
Msg( "Replay is not enabled.\n" );
|
|
return;
|
|
}
|
|
|
|
//------------------------- SERVER ONLY -------------------------
|
|
|
|
//
|
|
// TODO: There should be two paths through this function - one for when a user requests a SPECIFIC FILE,
|
|
// and one where the user wants file based on a dump of whatever was recorded in the last N seconds.
|
|
//
|
|
|
|
if ( !replay || !replay->m_DemoRecorder.IsRecording() )
|
|
{
|
|
SVC_Print msg( "The server is not currently recording replay demos.\n" );
|
|
CEngineSingleUserFilter filter( cmd_clientslot + 1, true );
|
|
sv.BroadcastMessage( msg, filter );
|
|
return;
|
|
}
|
|
|
|
// Make sure client slot is valid
|
|
if ( cmd_clientslot < 0 )
|
|
{
|
|
AssertMsg( 0, "request_replay_demo: Bad client slot." );
|
|
Warning( "request_replay_demo: Bad client slot, %d.", cmd_clientslot );
|
|
return;
|
|
}
|
|
|
|
// Already downloading a file?
|
|
// TODO: need to keep track of a user who is constantly requesting or is that automatic behavior?
|
|
if ( replay->m_vecClientsDownloading.IsBitSet( cmd_clientslot ) )
|
|
{
|
|
SVC_Print msg( "Request denied. You are already downloading.\n" );
|
|
CEngineSingleUserFilter filter( cmd_clientslot + 1, true );
|
|
sv.BroadcastMessage( msg, filter );
|
|
return;
|
|
}
|
|
|
|
// Set the download bit
|
|
// TODO
|
|
// replay->m_vecClientsDownloading.Set( cmd_clientslot, true );
|
|
|
|
// Dump current demo buffer to a .dem file for the client
|
|
char szFilename[MAX_OSPATH];
|
|
replay->m_DemoRecorder.GetUniqueDemoFilename( szFilename, sizeof(szFilename) );
|
|
replay->m_DemoRecorder.DumpToFile( szFilename );
|
|
|
|
// Add to history
|
|
// TODO: Pass in proper demo length here
|
|
// TODO: Write me.
|
|
extern ConVar replay_demolifespan;
|
|
CServerReplayHistoryEntryData *pNewServerEntry = new CServerReplayHistoryEntryData();
|
|
tm now;
|
|
Plat_GetLocalTime( &now );
|
|
time_t now_time_t = mktime( &now );
|
|
pNewServerEntry->m_nRecordTime = static_cast< uint64 >( now_time_t );
|
|
pNewServerEntry->m_nLifeSpan = replay_demolifespan.GetInt() * 24 * 3600;
|
|
pNewServerEntry->m_DemoLength.SetSeconds( 0 );
|
|
V_strcpy( pNewServerEntry->m_szFilename, szFilename );
|
|
V_strcpy( pNewServerEntry->m_szMapName, sv.GetMapName() );
|
|
pNewServerEntry->m_uClientSteamId = sv.Client( cmd_clientslot )->m_SteamID.ConvertToUint64();
|
|
pNewServerEntry->m_nBytesTransferred = 0;
|
|
pNewServerEntry->m_bTransferComplete = false;
|
|
pNewServerEntry->m_nSize = g_pFullFileSystem->Size( szFilename );
|
|
pNewServerEntry->m_bTransferring = false;
|
|
pNewServerEntry->m_nTransferId = -1;
|
|
pNewServerEntry->m_nFileStatus = CServerReplayHistoryEntryData::FILESTATUS_EXISTS;
|
|
g_pServerReplayHistoryManager->RecordEntry( pNewServerEntry );
|
|
|
|
// Extract the file stem
|
|
char szDemoStem[260];
|
|
Q_StripExtension( szFilename, szDemoStem, sizeof(szDemoStem) );
|
|
char szCommand[288];
|
|
Assert( replay->m_DemoRecorder.m_nStartTick >= 0 );
|
|
V_sprintf_safe(
|
|
szCommand, "replay_cache_ragdolls %s %d %d",
|
|
szDemoStem,
|
|
(int)replay->m_DemoRecorder.m_nStartTick,
|
|
g_pFullFileSystem->Size( szFilename )
|
|
); // NOTE: m_nStartTick is updated as the "oldest" frame's tick count
|
|
|
|
// Setup a message
|
|
CNETMsg_StringCmd_t msg( szCommand );
|
|
|
|
// Send the file stem back to the client for merging ragdoll/etc. with demo file
|
|
CEngineSingleUserFilter filter( cmd_clientslot + 1, true );
|
|
sv.BroadcastMessage( msg, filter );
|
|
|
|
Msg( "Wrote Replay demo file to disk, %s.\n", szFilename );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
CON_COMMAND_F( replay_cache_ragdolls, "Cache ragdolls to disk", FCVAR_HIDDEN | FCVAR_DONTRECORD | FCVAR_SERVER_CAN_EXECUTE )
|
|
{
|
|
if ( args.ArgC() != 4 )
|
|
{
|
|
AssertMsg( 0, "Bad number of arguments to replay_cache_ragdolls!\n" );
|
|
Warning( "Bad number of arguments to replay_cache_ragdolls!\n" );
|
|
return;
|
|
}
|
|
|
|
// Tell client to cache ragdolls
|
|
char szRagdollCacheFilename[MAX_OSPATH];
|
|
const char *pBaseFilename = args[1];
|
|
V_snprintf( szRagdollCacheFilename, sizeof( szRagdollCacheFilename ), "%s.dmx", pBaseFilename );
|
|
int nStartTick = V_atoi( args[2] );
|
|
g_ClientDLL->CacheReplayRagdolls( szRagdollCacheFilename, nStartTick );
|
|
|
|
char szDemoFilename[MAX_OSPATH];
|
|
V_snprintf( szDemoFilename, sizeof( szDemoFilename ), "%s.dem", pBaseFilename );
|
|
|
|
// Record in client history
|
|
extern ConVar replay_demolifespan;
|
|
CClientReplayHistoryEntryData *pNewEntry = new CClientReplayHistoryEntryData();
|
|
if ( !pNewEntry )
|
|
return;
|
|
tm now;
|
|
Plat_GetLocalTime( &now );
|
|
time_t now_time_t = mktime( &now );
|
|
pNewEntry->m_nRecordTime = static_cast< int >( now_time_t );
|
|
pNewEntry->m_nLifeSpan = replay_demolifespan.GetInt() * 24 * 3600;
|
|
pNewEntry->m_DemoLength.SetSeconds( 0 );
|
|
V_strcpy( pNewEntry->m_szFilename, szDemoFilename );
|
|
V_strcpy( pNewEntry->m_szMapName, GetBaseLocalClient().m_szLevelName );
|
|
V_strcpy( pNewEntry->m_szServerAddress, GetBaseLocalClient().m_NetChannel->GetAddress() );
|
|
pNewEntry->m_nBytesTransferred = 0;
|
|
pNewEntry->m_bTransferComplete = false;
|
|
pNewEntry->m_nSize = atoi( args[3] );
|
|
pNewEntry->m_bTransferring = false;
|
|
pNewEntry->m_nTransferId = -1;
|
|
if ( !g_pClientReplayHistoryManager->RecordEntry( pNewEntry ) )
|
|
{
|
|
Warning( "Replay: Failed to record entry.\n" );
|
|
return;
|
|
}
|
|
|
|
// Attempt to download immediately
|
|
pNewEntry->BeginDownload();
|
|
|
|
DevMsg( "Requesting file %s from %s ( %s ).\n", szDemoFilename, GetBaseLocalClient().m_NetChannel->GetName(), GetBaseLocalClient().m_NetChannel->GetAddress() );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
#endif
|