Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

451 lines
18 KiB

//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======
//
// Purpose: Uploads KeyValue stats to the new SteamWorks gamestats system.
//
//=============================================================================
#include "cbase.h"
#include "cdll_int.h"
#include "tier2/tier2.h"
#include <time.h>
#include "c_playerresource.h"
#include "steam/isteamutils.h"
#include "steamworks_gamestats_client.h"
#include "icommandline.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
ConVar steamworks_sessionid_client( "steamworks_sessionid_client", "0", FCVAR_HIDDEN, "The client session ID for the new steamworks gamestats." );
extern ConVar steamworks_sessionid_server;
static CSteamWorksGameStatsClient g_SteamWorksGameStatsClient;
extern ConVar developer;
void Show_Steam_Stats_Session_ID( void )
{
DevMsg( "Client session ID (%s).\n", steamworks_sessionid_client.GetString() );
DevMsg( "Server session ID (%s).\n", steamworks_sessionid_server.GetString() );
}
static ConCommand ShowSteamStatsSessionID( "ShowSteamStatsSessionID", Show_Steam_Stats_Session_ID, "Prints out the game stats session ID's (developer convar must be set to non-zero).", FCVAR_DEVELOPMENTONLY );
//-----------------------------------------------------------------------------
// Purpose: Clients store the server's session IDs so we can associate client rows with server rows.
//-----------------------------------------------------------------------------
void ServerSessionIDChangeCallback( IConVar *pConVar, const char *pOldString, float flOldValue )
{
ConVarRef var( pConVar );
if ( var.IsValid() )
{
// Treat the variable as a string, since the sessionID is 64 bit and the convar int interface is only 32 bit.
const char* pVarString = var.GetString();
uint64 newServerSessionID = Q_atoi64( pVarString );
GetSteamWorksGameStatsClient().SetServerSessionID( newServerSessionID );
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns a reference to the global object
//-----------------------------------------------------------------------------
CSteamWorksGameStatsClient& GetSteamWorksGameStatsClient()
{
return g_SteamWorksGameStatsClient;
}
//-----------------------------------------------------------------------------
// Purpose: Constructor. Sets up the steam callbacks accordingly depending on client/server dll
//-----------------------------------------------------------------------------
CSteamWorksGameStatsClient::CSteamWorksGameStatsClient() : BaseClass( "CSteamWorksGameStatsClient", "steamworks_sessionid_client" )
{
Reset();
}
//-----------------------------------------------------------------------------
// Purpose: Reset uploader state.
//-----------------------------------------------------------------------------
void CSteamWorksGameStatsClient::Reset()
{
BaseClass::Reset();
m_HumanCntInGame = 0;
m_FriendCntInGame = 0;
memset( m_pzPlayerName, 0, ARRAYSIZE(m_pzPlayerName) );
steamworks_sessionid_client.SetValue( 0 );
ClearServerSessionID();
}
//-----------------------------------------------------------------------------
// Purpose: Init function from CAutoGameSystemPerFrame and must return true.
//-----------------------------------------------------------------------------
bool CSteamWorksGameStatsClient::Init()
{
BaseClass::Init();
// TODO: This event doesn't exist in dota. Hook it up?
//ListenForGameEvent( "client_disconnect" );
steamworks_sessionid_server.InstallChangeCallback( ServerSessionIDChangeCallback );
return true;
}
void CSteamWorksGameStatsClient::AddSessionIDsToTable( int iTableID )
{
// Our client side session.
WriteInt64ToTable( m_SessionID, iTableID, "SessionID" );
// The session of the server we are connected to.
WriteInt64ToTable( m_ServerSessionID, iTableID, "ServerSessionID" );
}
//-----------------------------------------------------------------------------
// Purpose: Event handler for gathering basic info as well as ending sessions.
//-----------------------------------------------------------------------------
void CSteamWorksGameStatsClient::FireGameEvent( IGameEvent *event )
{
if ( !event )
return;
const char *pEventName = event->GetName();
if ( FStrEq( "client_disconnect", pEventName ) )
{
ClientDisconnect();
}
else if ( FStrEq( "player_changename", pEventName ) )
{
V_strncpy( m_pzPlayerName, event->GetString( "newname", "No Player Name" ), sizeof( m_pzPlayerName ) );
}
else
{
BaseClass::FireGameEvent( event );
}
}
//-----------------------------------------------------------------------------
// Purpose: Sets the server session ID but ONLY if it's not 0. We are using this to avoid a race
// condition where a server sends their session stats before a client does, thereby,
// resetting the client's server session ID to 0.
//-----------------------------------------------------------------------------
void CSteamWorksGameStatsClient::SetServerSessionID( uint64 serverSessionID )
{
if ( !serverSessionID )
return;
if ( serverSessionID != m_ActiveSession.m_ServerSessionID )
{
m_ActiveSession.m_ServerSessionID = serverSessionID;
m_ActiveSession.m_ConnectTime = GetTimeSinceEpoch();
m_ActiveSession.m_DisconnectTime = 0;
m_iServerConnectCount++;
}
m_ServerSessionID = serverSessionID;
}
//-----------------------------------------------------------------------------
// Purpose: Writes the disconnect time to the current server session entry.
//-----------------------------------------------------------------------------
void CSteamWorksGameStatsClient::ClientDisconnect()
{
Assert( 0 ); // i want to remove this.
if ( m_ActiveSession.m_ServerSessionID == 0 )
return;
m_SteamWorksInterface = GetInterface();
if ( !m_SteamWorksInterface )
return;
if ( !IsCollectingAnyData() )
return;
uint64 ulRowID = 0;
m_SteamWorksInterface->AddNewRow( &ulRowID, m_SessionID, "ClientSessionLookup" );
WriteInt64ToTable( m_SessionID, ulRowID, "SessionID" );
WriteInt64ToTable( m_ActiveSession.m_ServerSessionID, ulRowID, "ServerSessionID" );
WriteIntToTable( m_ActiveSession.m_ConnectTime, ulRowID, "ConnectTime" );
WriteIntToTable( GetTimeSinceEpoch(), ulRowID, "DisconnectTime" );
m_SteamWorksInterface->CommitRow( ulRowID );
m_ActiveSession.Reset();
}
//-----------------------------------------------------------------------------
// Purpose: Uploads any end of session rows.
//-----------------------------------------------------------------------------
void CSteamWorksGameStatsClient::WriteSessionRow()
{
m_SteamWorksInterface = GetInterface();
if ( !m_SteamWorksInterface )
return;
m_SteamWorksInterface->AddSessionAttributeInt64( m_SessionID, "ServerSessionID", m_ServerSessionID );
m_SteamWorksInterface->AddSessionAttributeString( m_SessionID, "ServerIP", m_pzServerIP );
m_SteamWorksInterface->AddSessionAttributeString( m_SessionID, "ServerName", m_pzHostName );
m_SteamWorksInterface->AddSessionAttributeString( m_SessionID, "StartMap", m_pzMapStart );
m_SteamWorksInterface->AddSessionAttributeString( m_SessionID, "PlayerName", m_pzPlayerName );
m_SteamWorksInterface->AddSessionAttributeInt( m_SessionID, "PlayersInGame", m_HumanCntInGame );
m_SteamWorksInterface->AddSessionAttributeInt( m_SessionID, "FriendsInGame", m_FriendCntInGame );
BaseClass::WriteSessionRow();
}
void CSteamWorksGameStatsClient::OnSteamSessionIssued( GameStatsSessionIssued_t *pResult, bool bError )
{
BaseClass::OnSteamSessionIssued( pResult, bError );
m_FriendCntInGame = GetFriendCountInGame();
CBasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
const char *pPlayerName = pPlayer ? pPlayer->GetPlayerName() : "unknown";
V_strncpy( m_pzPlayerName, pPlayerName, sizeof( m_pzPlayerName ) );
}
//-----------------------------------------------------------------------------
// Purpose: Reports client's perf data at the end of a client session.
//-----------------------------------------------------------------------------
void CSteamWorksGameStatsClient::AddClientPerfData( KeyValues *pKV )
{
m_SteamWorksInterface = GetInterface();
if ( !m_SteamWorksInterface )
return;
if ( !IsCollectingAnyData() )
return;
RTime32 currentTime = GetTimeSinceEpoch();
uint64 uSessionID = m_SessionID;
uint64 ulRowID = 0;
m_SteamWorksInterface->AddNewRow( &ulRowID, uSessionID, "CSGOClientPerfData" );
if ( !ulRowID )
return;
WriteInt64ToTable( m_SessionID, ulRowID, "SessionID" );
// WriteInt64ToTable( m_ServerSessionID, ulRowID, "ServerSessionID" );
WriteIntToTable( currentTime, ulRowID, "TimeSubmitted" );
//WriteStringToTable( pKV->GetString( "Map/mapname" ), ulRowID, "MapID");
WriteIntToTable( pKV->GetInt( "appid" ), ulRowID, "AppID");
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/AvgFPS" ), ulRowID, "AvgFPS");
WriteFloatToTable( pKV->GetFloat( "map/perfdata/MinFPS" ), ulRowID, "MinFPS");
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/MaxFPS" ), ulRowID, "MaxFPS");
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/StdDevFPS" ), ulRowID, "StdDevFPS");
WriteInt64ToTable( pKV->GetUint64( "Map/perfdata/FrameHistAll" ), ulRowID, "FrameHistAll" );
WriteInt64ToTable( pKV->GetUint64( "Map/perfdata/FrameHistGame1" ), ulRowID, "FrameHistGame1" );
WriteInt64ToTable( pKV->GetUint64( "Map/perfdata/FrameHistGame2" ), ulRowID, "FrameHistGame2" );
WriteInt64ToTable( pKV->GetUint64( "Map/perfdata/FrameHistGame3" ), ulRowID, "FrameHistGame3" );
WriteInt64ToTable( pKV->GetUint64( "Map/perfdata/FrameHistGame4" ), ulRowID, "FrameHistGame4" );
WriteStringToTable( pKV->GetString( "CPUID" ), ulRowID, "CPUID" );
WriteFloatToTable( pKV->GetFloat( "CPUGhz" ), ulRowID, "CPUGhz");
WriteInt64ToTable( pKV->GetUint64( "CPUModel" ), ulRowID, "CPUModel" );
WriteInt64ToTable( pKV->GetUint64( "CPUFeatures0" ), ulRowID, "CPUFeatures0" );
WriteInt64ToTable( pKV->GetUint64( "CPUFeatures1" ), ulRowID, "CPUFeatures1" );
WriteInt64ToTable( pKV->GetUint64( "CPUFeatures2" ), ulRowID, "CPUFeatures2" );
WriteIntToTable( pKV->GetInt( "NumCores" ), ulRowID, "NumCores" );
WriteStringToTable( pKV->GetString( "GPUDrv" ), ulRowID, "GPUDrv");
WriteIntToTable( pKV->GetInt( "GPUVendor" ), ulRowID, "GPUVendor");
WriteIntToTable( pKV->GetInt( "GPUDeviceID" ), ulRowID, "GPUDeviceID");
WriteIntToTable( pKV->GetInt( "GPUDriverVersion" ), ulRowID, "GPUDriverVersion");
WriteIntToTable( pKV->GetInt( "DxLvl" ), ulRowID, "DxLvl");
WriteIntToTable( pKV->GetInt( "IsSplitScreen" ), ulRowID, "IsSplitScreen");
WriteIntToTable( pKV->GetBool( "Map/Windowed" ), ulRowID, "Windowed");
WriteIntToTable( pKV->GetBool( "Map/WindowedNoBorder" ), ulRowID, "WindowedNoBorder");
WriteIntToTable( pKV->GetInt( "width" ), ulRowID, "Width");
WriteIntToTable( pKV->GetInt( "height" ), ulRowID, "Height");
WriteIntToTable( pKV->GetInt( "Map/UsedVoice" ), ulRowID, "Usedvoiced");
WriteStringToTable( pKV->GetString( "Map/Language" ), ulRowID, "Language");
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/AvgServerPing" ), ulRowID, "AvgServerPing");
WriteIntToTable( pKV->GetInt( "Map/Caption" ), ulRowID, "IsCaptioned");
WriteIntToTable( pKV->GetInt( "IsPC" ), ulRowID, "IsPC");
WriteIntToTable( pKV->GetInt( "Map/Cheats" ), ulRowID, "Cheats");
WriteIntToTable( pKV->GetInt( "Map/MapTime" ), ulRowID, "MapTime");
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/AvgMainThreadTime" ), ulRowID, "AvgMainThreadTime");
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/StdDevMainThreadTime" ), ulRowID, "StdMainThreadTime");
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/AvgMainThreadWaitTime" ), ulRowID, "AvgMainThreadWaitTime" );
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/StdDevMainThreadWaitTime" ), ulRowID, "StdMainThreadWaitTime" );
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/AvgRenderThreadTime" ), ulRowID, "AvgRenderThreadTime" );
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/StdDevRenderThreadTime" ), ulRowID, "StdRenderThreadTime" );
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/AvgRenderThreadWaitTime" ), ulRowID, "AvgRenderThreadWaitTime" );
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/StdDevRenderThreadWaitTime" ), ulRowID, "StdRenderThreadWaitTime" );
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/PercentageCPUThrottledToLevel1" ), ulRowID, "PercentageCPUThrottledToLevel1");
WriteFloatToTable( pKV->GetFloat( "Map/perfdata/PercentageCPUThrottledToLevel2" ), ulRowID, "PercentageCPUThrottledToLevel2");
m_SteamWorksInterface->CommitRow( ulRowID );
}
//-----------------------------------------------------------------------------
// Purpose: Reports client's game event stats.
//-----------------------------------------------------------------------------
bool CSteamWorksGameStatsClient::AddCsgoGameEventStat( char const *szMapName, char const *szEvent, Vector const &pos, QAngle const &ang, uint64 ullData, int16 nRound, int16 numRoundSecondsElapsed )
{
m_SteamWorksInterface = GetInterface();
if ( !m_SteamWorksInterface )
return false;
if ( !IsCollectingAnyData() )
return false;
// UNUSED: RTime32 currentTime = GetTimeSinceEpoch();
uint64 uSessionID = m_SessionID;
uint64 ulRowID = 0;
m_SteamWorksInterface->AddNewRow( &ulRowID, uSessionID, "CSGOGameEvent" );
if ( !ulRowID )
return false;
WriteInt64ToTable( m_SessionID, ulRowID, "SessionID" );
static int s_nCounter = 0;
WriteIntToTable( ++ s_nCounter, ulRowID, "EventCount" );
WriteStringToTable( szEvent, ulRowID, "EventID" );
WriteStringToTable( szMapName, ulRowID, "MapID" );
WriteIntToTable( (int16) Clamp<float>( pos[0], -MAX_COORD_FLOAT, MAX_COORD_FLOAT ), ulRowID, "PosX" );
WriteIntToTable( (int16) Clamp<float>( pos[1], -MAX_COORD_FLOAT, MAX_COORD_FLOAT ), ulRowID, "PosY" );
WriteIntToTable( (int16) Clamp<float>( pos[2], -MAX_COORD_FLOAT, MAX_COORD_FLOAT ), ulRowID, "PosZ" );
WriteFloatToTable( ang[0], ulRowID, "ViewX" );
WriteFloatToTable( ang[1], ulRowID, "ViewY" );
WriteFloatToTable( ang[2], ulRowID, "ViewZ" );
WriteIntToTable( nRound, ulRowID, "GameRound" );
WriteIntToTable( numRoundSecondsElapsed, ulRowID, "GameTime" );
WriteInt64ToTable( ullData, ulRowID, "Data" );
m_SteamWorksInterface->CommitRow( ulRowID );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Reports client's VPK load stats.
//-----------------------------------------------------------------------------
void CSteamWorksGameStatsClient::AddVPKLoadStats( KeyValues *pKV )
{
m_SteamWorksInterface = GetInterface();
if ( !m_SteamWorksInterface )
return;
if ( !IsCollectingAnyData() )
return;
RTime32 currentTime = GetTimeSinceEpoch();
uint64 uSessionID = m_SessionID;
uint64 ulRowID = 0;
m_SteamWorksInterface->AddNewRow( &ulRowID, uSessionID, "CSGOClientVPKFileStats" );
if ( !ulRowID )
return;
WriteInt64ToTable( m_SessionID, ulRowID, "SessionID" );
WriteIntToTable( currentTime, ulRowID, "TimeSubmitted" );
WriteIntToTable( pKV->GetInt( "BytesReadFromCache" ), ulRowID, "BytesReadFromCache");
WriteIntToTable( pKV->GetInt( "ItemsReadFromCache" ), ulRowID, "ItemsReadFromCache");
WriteIntToTable( pKV->GetInt( "DiscardsFromCache" ), ulRowID, "DiscardsFromCache");
WriteIntToTable( pKV->GetInt( "AddedToCache" ), ulRowID, "AddedToCache");
WriteIntToTable( pKV->GetInt( "CacheMisses" ), ulRowID, "CacheMisses");
WriteIntToTable( pKV->GetInt( "FileErrorCount" ), ulRowID, "FileErrorCount");
WriteIntToTable( pKV->GetInt( "FileErrorsCorrected" ), ulRowID, "FileErrorsCorrected");
WriteIntToTable( pKV->GetInt( "FileResultsDifferent" ), ulRowID, "FileResultsDifferent");
m_SteamWorksInterface->CommitRow( ulRowID );
}
//-----------------------------------------------------------------------------
// Purpose: Reports any VPK file load errors
//-----------------------------------------------------------------------------
void CSteamWorksGameStatsClient::AddVPKFileLoadErrorData( KeyValues *pKV )
{
m_SteamWorksInterface = GetInterface();
if ( !m_SteamWorksInterface )
return;
if ( !IsCollectingAnyData() )
return;
RTime32 currentTime = GetTimeSinceEpoch();
uint64 uSessionID = m_SessionID;
uint64 ulRowID = 0;
for ( KeyValues *pkvSubKey = pKV->GetFirstTrueSubKey(); pkvSubKey != NULL; pkvSubKey = pkvSubKey->GetNextTrueSubKey() )
{
m_SteamWorksInterface->AddNewRow( &ulRowID, uSessionID, "CSGOClientVPKFileError" );
if ( !ulRowID )
return;
WriteInt64ToTable( m_SessionID, ulRowID, "SessionID" );
WriteIntToTable( currentTime, ulRowID, "TimeSubmitted" );
WriteIntToTable( pkvSubKey->GetInt( "PackFileID" ), ulRowID, "PackFileID");
WriteIntToTable( pkvSubKey->GetInt( "PackFileNumber" ), ulRowID, "PackFileNumber");
WriteIntToTable( pkvSubKey->GetInt( "FileFraction" ), ulRowID, "FileFraction");
WriteStringToTable( pkvSubKey->GetString( "ChunkMd5Master" ), ulRowID, "ChunkMd5MasterText");
WriteStringToTable( pkvSubKey->GetString( "ChunkMd5First" ), ulRowID, "ChunkMd5FirstText");
WriteStringToTable( pkvSubKey->GetString( "ChunkMd5Second" ), ulRowID, "ChunkMd5SecondText");
m_SteamWorksInterface->CommitRow( ulRowID );
}
}
//-------------------------------------------------------------------------------------------------
/**
* Purpose: Calculates the number of friends in the game
*/
int CSteamWorksGameStatsClient::GetFriendCountInGame()
{
// Get the number of steam friends in game
int friendsInOurGame = 0;
#if !defined( NO_STEAM )
// Do we have access to the steam API?
if ( AccessToSteamAPI() )
{
CSteamID m_SteamID = steamapicontext->SteamUser()->GetSteamID();
// Let's get our game info so we can use that to test if our friends are connected to the same game as us
FriendGameInfo_t myGameInfo;
steamapicontext->SteamFriends()->GetFriendGamePlayed( m_SteamID, &myGameInfo );
CSteamID myLobby = steamapicontext->SteamMatchmaking()->GetLobbyOwner( myGameInfo.m_steamIDLobby );
// This returns the number of friends that are playing a game
int activeFriendCnt = steamapicontext->SteamFriends()->GetFriendCount( k_EFriendFlagImmediate );
// Check each active friend's lobby ID to see if they are in our game
for ( int h=0; h< activeFriendCnt ; ++h )
{
FriendGameInfo_t friendInfo;
CSteamID friendID = steamapicontext->SteamFriends()->GetFriendByIndex( h, k_EFriendFlagImmediate );
if ( steamapicontext->SteamFriends()->GetFriendGamePlayed( friendID, &friendInfo ) )
{
// Does our friend have a valid lobby ID?
if ( friendInfo.m_gameID.IsValid() )
{
// Get our friend's lobby info
CSteamID friendLobby = steamapicontext->SteamMatchmaking()->GetLobbyOwner( friendInfo.m_steamIDLobby );
// Double check the validity of the friend lobby ID then check to see if they are in our game
if ( friendLobby.IsValid() && myLobby == friendLobby )
{
++friendsInOurGame;
}
}
}
}
}
#endif // !NO_STEAM
return friendsInOurGame;
}