|
|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
|
|
//
|
|
|
|
//=======================================================================================//
|
|
|
|
|
|
|
|
#include "cbase.h"
|
|
|
|
|
|
|
|
#if defined( REPLAY_ENABLED )
|
|
|
|
|
|
|
|
#include "genericclassbased_replay.h"
|
|
|
|
#include "clientmode_shared.h"
|
|
|
|
#include "replay/ireplaymoviemanager.h"
|
|
|
|
#include "replay/ireplayfactory.h"
|
|
|
|
#include "replay/ireplayscreenshotmanager.h"
|
|
|
|
#include "replay/screenshot.h"
|
|
|
|
#include "replay/gamedefs.h"
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
extern IReplayScreenshotManager *g_pReplayScreenshotManager;
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
CGenericClassBasedReplay::CGenericClassBasedReplay()
|
|
|
|
: m_nPlayerTeam( 0 )
|
|
|
|
{
|
|
|
|
m_szKillerName[ 0 ] = 0;
|
|
|
|
|
|
|
|
m_nPlayerClass = REPLAY_CLASS_UNDEFINED;
|
|
|
|
m_nKillerClass = REPLAY_CLASS_UNDEFINED;
|
|
|
|
}
|
|
|
|
|
|
|
|
CGenericClassBasedReplay::~CGenericClassBasedReplay()
|
|
|
|
{
|
|
|
|
OnEndRecording();
|
|
|
|
|
|
|
|
m_vecKills.PurgeAndDeleteElements();
|
|
|
|
m_vecDominations.PurgeAndDeleteElements();
|
|
|
|
m_vecAssisterDominations.PurgeAndDeleteElements();
|
|
|
|
m_vecRevenges.PurgeAndDeleteElements();
|
|
|
|
m_vecAssisterRevenges.PurgeAndDeleteElements();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::OnBeginRecording()
|
|
|
|
{
|
|
|
|
BaseClass::OnBeginRecording();
|
|
|
|
|
|
|
|
Assert( gameeventmanager );
|
|
|
|
ListenForGameEvent( "player_death" );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::OnEndRecording()
|
|
|
|
{
|
|
|
|
if ( gameeventmanager )
|
|
|
|
{
|
|
|
|
gameeventmanager->RemoveListener( this );
|
|
|
|
}
|
|
|
|
|
|
|
|
BaseClass::OnEndRecording();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::OnComplete()
|
|
|
|
{
|
|
|
|
BaseClass::OnComplete();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CGenericClassBasedReplay::ShouldAllowDelete() const
|
|
|
|
{
|
|
|
|
return g_pClientReplayContext->GetMovieManager()->GetNumMoviesDependentOnReplay( this ) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::OnDelete()
|
|
|
|
{
|
|
|
|
BaseClass::OnDelete();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::FireGameEvent( IGameEvent *pEvent )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::Update()
|
|
|
|
{
|
|
|
|
BaseClass::Update();
|
|
|
|
|
|
|
|
// Record any new stats
|
|
|
|
RecordUpdatedStats();
|
|
|
|
|
|
|
|
// Setup next update
|
|
|
|
m_flNextUpdateTime = engine->Time() + .1f;
|
|
|
|
}
|
|
|
|
|
|
|
|
float CGenericClassBasedReplay::GetKillScreenshotDelay()
|
|
|
|
{
|
|
|
|
ConVarRef replay_screenshotkilldelay( "replay_screenshotkilldelay" );
|
|
|
|
return replay_screenshotkilldelay.IsValid() ? replay_screenshotkilldelay.GetFloat() : 0.5f;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::RecordUpdatedStats()
|
|
|
|
{
|
|
|
|
// Get current stats
|
|
|
|
static RoundStats_t s_curStats;
|
|
|
|
if ( !GetCurrentStats( s_curStats ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Go through each stat and see if it's changed
|
|
|
|
for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
|
|
|
|
{
|
|
|
|
const int nCurStat = s_curStats.Get( i );
|
|
|
|
const int nRefStat = m_refStats.Get( i );
|
|
|
|
|
|
|
|
if ( nCurStat != nRefStat )
|
|
|
|
{
|
|
|
|
// Calculate new stat based on reference
|
|
|
|
const int nLifeStat = nCurStat - nRefStat;
|
|
|
|
|
|
|
|
if ( nLifeStat != m_lifeStats.Get( i ) )
|
|
|
|
{
|
|
|
|
ConVarRef replay_debug( "replay_debug" );
|
|
|
|
if ( replay_debug.IsValid() && replay_debug.GetBool() )
|
|
|
|
{
|
|
|
|
Msg( "REPLAY: Player stat \"%s\" changed from %i to %i.\n", GetStatString( i ), nRefStat, nCurStat );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the new stat
|
|
|
|
m_lifeStats.Set( i, nLifeStat );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CGenericClassBasedReplay::Read( KeyValues *pIn )
|
|
|
|
{
|
|
|
|
if ( !BaseClass::Read( pIn ) )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Read player class
|
|
|
|
m_nPlayerClass = pIn->GetInt( "player_class" ); Assert( IsValidClass( m_nPlayerClass ) );
|
|
|
|
|
|
|
|
// Read player team
|
|
|
|
m_nPlayerTeam = pIn->GetInt( "player_team" ); Assert( IsValidTeam( m_nPlayerTeam ) );
|
|
|
|
|
|
|
|
// Read killer info
|
|
|
|
m_nKillerClass = pIn->GetInt( "killer_class" );
|
|
|
|
V_strcpy( m_szKillerName, pIn->GetString( "killer_name" ) );
|
|
|
|
|
|
|
|
// Make sure vector is clear
|
|
|
|
Assert( GetKillCount() == 0 );
|
|
|
|
|
|
|
|
// Read all kill data and add the kills vector
|
|
|
|
KeyValues *pKills = pIn->FindKey( "kills" );
|
|
|
|
if ( pKills )
|
|
|
|
{
|
|
|
|
FOR_EACH_TRUE_SUBKEY( pKills, pKill )
|
|
|
|
{
|
|
|
|
// Create the kill data
|
|
|
|
AddKill(
|
|
|
|
pKill->GetString( "victim_name" ),
|
|
|
|
pKill->GetInt( "victim_class" )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AddKillStats( m_vecDominations , pIn, "dominations", REPLAY_GAMESTATS_DOMINATIONS );
|
|
|
|
AddKillStats( m_vecAssisterDominations, pIn, "assister_dominations", REPLAY_GAMESTATS_UNDEFINED );
|
|
|
|
AddKillStats( m_vecRevenges , pIn, "revenges", REPLAY_GAMESTATS_REVENGE );
|
|
|
|
AddKillStats( m_vecAssisterRevenges , pIn, "assister_revenges", REPLAY_GAMESTATS_UNDEFINED );
|
|
|
|
|
|
|
|
// Read stats by index
|
|
|
|
KeyValues *pStats = pIn->FindKey( "stats" );
|
|
|
|
if ( pStats )
|
|
|
|
{
|
|
|
|
for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
|
|
|
|
{
|
|
|
|
char szStatKey[ 16 ];
|
|
|
|
V_snprintf( szStatKey, sizeof( szStatKey ), "%i", i );
|
|
|
|
m_lifeStats.Set( i, pStats->GetInt( szStatKey ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::AddKillStats( CUtlVector< GenericStatInfo_t * > &vecKillStats, KeyValues *pIn, const char *pSubKeyName, int iStatIndex )
|
|
|
|
{
|
|
|
|
Assert( vecKillStats.Count() == 0 );
|
|
|
|
KeyValues *pSubKey = pIn->FindKey( pSubKeyName );
|
|
|
|
if ( pSubKey )
|
|
|
|
{
|
|
|
|
FOR_EACH_TRUE_SUBKEY( pSubKey, pCurKillStat )
|
|
|
|
{
|
|
|
|
GenericStatInfo_t *pNewKillStat = new GenericStatInfo_t;
|
|
|
|
pNewKillStat->m_nVictimFriendId = pCurKillStat->GetInt( "victim_friend_id" );
|
|
|
|
pNewKillStat->m_nAssisterFriendId = pCurKillStat->GetInt( "assister_friend_id" );
|
|
|
|
vecKillStats.AddToTail( pNewKillStat );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Duplicate the data in the life stats
|
|
|
|
if ( iStatIndex > m_nStatUndefined )
|
|
|
|
{
|
|
|
|
m_lifeStats.Set( iStatIndex, vecKillStats.Count() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::Write( KeyValues *pOut )
|
|
|
|
{
|
|
|
|
BaseClass::Write( pOut );
|
|
|
|
|
|
|
|
// Write player class
|
|
|
|
pOut->SetInt( "player_class", m_nPlayerClass );
|
|
|
|
|
|
|
|
// Write player team
|
|
|
|
pOut->SetInt( "player_team", m_nPlayerTeam );
|
|
|
|
|
|
|
|
// Write killer info
|
|
|
|
pOut->SetInt( "killer_class", m_nKillerClass );
|
|
|
|
pOut->SetString( "killer_name", m_szKillerName );
|
|
|
|
|
|
|
|
// Write kills
|
|
|
|
KeyValues *pKills = new KeyValues( "kills" );
|
|
|
|
pOut->AddSubKey( pKills );
|
|
|
|
|
|
|
|
for ( int i = 0; i < GetKillCount(); ++i )
|
|
|
|
{
|
|
|
|
KillData_t *pCurKill = m_vecKills[ i ];
|
|
|
|
|
|
|
|
KeyValues *pKillOut = new KeyValues( "kill" );
|
|
|
|
pKills->AddSubKey( pKillOut );
|
|
|
|
|
|
|
|
// Write kill data
|
|
|
|
pKillOut->SetString( "victim_name", pCurKill->m_szPlayerName );
|
|
|
|
pKillOut->SetInt( "victim_class", pCurKill->m_nPlayerClass );
|
|
|
|
}
|
|
|
|
|
|
|
|
WriteKillStatVector( m_vecDominations , "dominations" , "domination" , pOut, 1 );
|
|
|
|
WriteKillStatVector( m_vecAssisterDominations, "assister_dominations", "assister_domination", pOut, 2 );
|
|
|
|
WriteKillStatVector( m_vecRevenges , "revenges" , "revenge" , pOut, 1 );
|
|
|
|
WriteKillStatVector( m_vecAssisterRevenges , "assister_revenges" , "assister_revenge" , pOut, 2 );
|
|
|
|
|
|
|
|
// Write non-zero stats by index
|
|
|
|
KeyValues *pStats = new KeyValues( "stats" );
|
|
|
|
pOut->AddSubKey( pStats );
|
|
|
|
|
|
|
|
for ( int i = 0; i < REPLAY_GAMESTATS_MAX; ++i )
|
|
|
|
{
|
|
|
|
const int nCurStat = m_lifeStats.Get( i );
|
|
|
|
if ( nCurStat )
|
|
|
|
{
|
|
|
|
char szStatKey[ 16 ];
|
|
|
|
V_snprintf( szStatKey, sizeof( szStatKey ), "%i", i );
|
|
|
|
pStats->SetInt( szStatKey, nCurStat );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::WriteKillStatVector( CUtlVector< CGenericClassBasedReplay::GenericStatInfo_t * > const &vec, const char *pSubKeyName,
|
|
|
|
const char *pElementKeyName, KeyValues *pRootKey, int nNumMembersToWrite ) const
|
|
|
|
{
|
|
|
|
Assert( nNumMembersToWrite >= 1 );
|
|
|
|
|
|
|
|
// Write dominations
|
|
|
|
KeyValues *pSubKey = new KeyValues( pSubKeyName );
|
|
|
|
pRootKey->AddSubKey( pSubKey );
|
|
|
|
|
|
|
|
for ( int i = 0; i < vec.Count(); ++i )
|
|
|
|
{
|
|
|
|
GenericStatInfo_t *pSrcData = vec[ i ];
|
|
|
|
|
|
|
|
KeyValues *pCurSubKey = new KeyValues( pElementKeyName );
|
|
|
|
pSubKey->AddSubKey( pCurSubKey );
|
|
|
|
|
|
|
|
// Always write
|
|
|
|
pCurSubKey->SetInt( "victim_friend_id", pSrcData->m_nVictimFriendId );
|
|
|
|
|
|
|
|
if ( nNumMembersToWrite > 1 )
|
|
|
|
{
|
|
|
|
pCurSubKey->SetInt( "assister_friend_id", pSrcData->m_nAssisterFriendId );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::AddKill( const char *pPlayerName, int nPlayerClass )
|
|
|
|
{
|
|
|
|
KillData_t *pNewKillData = new KillData_t;
|
|
|
|
|
|
|
|
V_strcpy( pNewKillData->m_szPlayerName , pPlayerName );
|
|
|
|
pNewKillData->m_nPlayerClass = nPlayerClass;
|
|
|
|
|
|
|
|
ConVarRef replay_debug( "replay_debug" );
|
|
|
|
if ( replay_debug.IsValid() && replay_debug.GetBool() )
|
|
|
|
{
|
|
|
|
DevMsg( "\n\nRecorded kill: name=%s, class=%s (this=%i)\n\n", pPlayerName, GetPlayerClass( nPlayerClass ), (int)this );
|
|
|
|
}
|
|
|
|
|
|
|
|
m_vecKills.AddToTail( pNewKillData );
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *CGenericClassBasedReplay::GetPlayerClass() const
|
|
|
|
{
|
|
|
|
return GetPlayerClass( m_nPlayerClass );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::AddDomination( int nVictimID )
|
|
|
|
{
|
|
|
|
AddKillStatFromUserIds( m_vecDominations, nVictimID );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::AddAssisterDomination( int nVictimID, int nAssiterID )
|
|
|
|
{
|
|
|
|
AddKillStatFromUserIds( m_vecAssisterDominations, nVictimID, nAssiterID );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::AddRevenge( int nVictimID )
|
|
|
|
{
|
|
|
|
AddKillStatFromUserIds( m_vecRevenges, nVictimID );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::AddAssisterRevenge( int nVictimID, int nAssiterID )
|
|
|
|
{
|
|
|
|
AddKillStatFromUserIds( m_vecAssisterRevenges, nVictimID, nAssiterID );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::AddKillStatFromUserIds( CUtlVector< GenericStatInfo_t * > &vec, int nVictimId, int nAssisterId/*=0*/ )
|
|
|
|
{
|
|
|
|
uint32 nVictimFriendId;
|
|
|
|
if ( !GetFriendIdFromUserId( engine->GetPlayerForUserID( nVictimId ), nVictimFriendId ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
uint32 nAssisterFriendId = 0;
|
|
|
|
if ( nAssisterId && !GetFriendIdFromUserId( engine->GetPlayerForUserID( nAssisterId ), nAssisterFriendId ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
AddKillStatFromFriendIds( vec, nVictimFriendId, nAssisterFriendId );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::AddKillStatFromFriendIds( CUtlVector< GenericStatInfo_t * > &vec, uint32 nVictimFriendId, uint32 nAssisterFriendId/*=0*/ )
|
|
|
|
{
|
|
|
|
GenericStatInfo_t *pNewKillStat = new GenericStatInfo_t;
|
|
|
|
pNewKillStat->m_nVictimFriendId = nVictimFriendId;
|
|
|
|
pNewKillStat->m_nAssisterFriendId = nAssisterFriendId;
|
|
|
|
vec.AddToTail( pNewKillStat );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CGenericClassBasedReplay::GetFriendIdFromUserId( int nPlayerIndex, uint32 &nFriendIdOut ) const
|
|
|
|
{
|
|
|
|
player_info_t pi;
|
|
|
|
if ( !steamapicontext->SteamFriends() ||
|
|
|
|
!steamapicontext->SteamUtils() ||
|
|
|
|
!engine->GetPlayerInfo( nPlayerIndex, &pi ) )
|
|
|
|
{
|
|
|
|
AssertMsg( 0, "REPLAY: Failed to add domination" );
|
|
|
|
nFriendIdOut = 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nFriendIdOut = pi.friendsID;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *CGenericClassBasedReplay::GetMaterialFriendlyPlayerClass() const
|
|
|
|
{
|
|
|
|
return GetPlayerClass();
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *CGenericClassBasedReplay::GetKillerName() const
|
|
|
|
{
|
|
|
|
Assert( WasKilled() );
|
|
|
|
return m_szKillerName;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *CGenericClassBasedReplay::GetKillerClass() const
|
|
|
|
{
|
|
|
|
Assert( WasKilled() );
|
|
|
|
return GetPlayerClass( m_nKillerClass );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::DumpGameSpecificData() const
|
|
|
|
{
|
|
|
|
DevMsg( " class: %s\n", GetPlayerClass() );
|
|
|
|
|
|
|
|
// Print kills
|
|
|
|
DevMsg( " %i kills:\n", GetKillCount() );
|
|
|
|
for ( int i = 0; i < GetKillCount(); ++i )
|
|
|
|
{
|
|
|
|
KillData_t *pCurKill = m_vecKills[ i ];
|
|
|
|
Msg( " kill %i: name=%s class=%s\n", i, pCurKill->m_szPlayerName, GetPlayerClass( pCurKill->m_nPlayerClass ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !WasKilled() )
|
|
|
|
{
|
|
|
|
Msg( " No killer.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Print killer info
|
|
|
|
Msg( " killer: name=%s class=%s\n", m_szKillerName, GetPlayerClass( m_nKillerClass ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::SetPlayerClass( int nPlayerClass )
|
|
|
|
{
|
|
|
|
//Assert( IsValidClass( nPlayerClass ) );
|
|
|
|
m_nPlayerClass = nPlayerClass;
|
|
|
|
|
|
|
|
// Setup reference stats if this is a valid class
|
|
|
|
if ( IsValidClass( nPlayerClass ) )
|
|
|
|
{
|
|
|
|
GetCurrentStats( m_refStats );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::SetPlayerTeam( int nPlayerTeam )
|
|
|
|
{
|
|
|
|
m_nPlayerTeam = nPlayerTeam;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGenericClassBasedReplay::RecordPlayerDeath( const char *pKillerName, int nKillerClass )
|
|
|
|
{
|
|
|
|
V_strcpy( m_szKillerName, pKillerName );
|
|
|
|
m_nKillerClass = nKillerClass;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#endif
|