// Copyright (C) 2007 Id Software, Inc. // #include "../precompiled.h" #pragma hdrstop #if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE ) #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #include "StatsTracker.h" #include "../rules/GameRules.h" #include "../Player.h" #include "../../sdnet/SDNet.h" #include "../../sdnet/SDNetStatsManager.h" #include "../../sdnet/SDNetAccount.h" #include "../../sdnet/SDNetUser.h" #include "../../sdnet/SDNetProfile.h" const char* sdStatsTracker::lifeStatsData_t::GetName( void ) const { return gameLocal.lifeStats[ index ].stat.c_str(); } /* =============================================================================== sdPlayerStatEntry_Float =============================================================================== */ /* ================ sdPlayerStatEntry_Float::sdPlayerStatEntry_Float ================ */ sdPlayerStatEntry_Float::sdPlayerStatEntry_Float( sdNetStatKeyValue::statValueType _type ) : sdPlayerStatEntry( _type ) { assert( _type == sdNetStatKeyValue::SVT_FLOAT || _type == sdNetStatKeyValue::SVT_FLOAT_MAX ); for ( int i = 0; i < MAX_CLIENTS; i++ ) { Clear( i ); } } /* ================ sdPlayerStatEntry_Float::Clear ================ */ void sdPlayerStatEntry_Float::Clear( int playerIndex ) { values[ playerIndex ] = 0; baseValues[ playerIndex ] = 0; } /* ================ sdPlayerStatEntry_Float::IncreaseValue ================ */ void sdPlayerStatEntry_Float::IncreaseValue( int playerIndex, const statValue_t& value ) { if ( gameLocal.rules->IsGameOn() ) { values[ playerIndex ] += value.GetFloat(); } } /* ================ sdPlayerStatEntry_Float::Display ================ */ void sdPlayerStatEntry_Float::Display( const char* name ) const { gameLocal.Printf( "%s\n", name ); for ( int i = 0; i < MAX_CLIENTS; i++ ) { gameLocal.Printf( "%f ", values[ i ] ); } for ( int i = 0; i < MAX_CLIENTS; i++ ) { gameLocal.Printf( "%f ", baseValues[ i ] ); } gameLocal.Printf( "\n" ); } /* ================ sdPlayerStatEntry_Float::Write ================ */ void sdPlayerStatEntry_Float::Write( idFile* fp, int playerIndex, const char* name ) const { fp->WriteFloatString( "%s: %f\n", name, values[ playerIndex ] ); } /* ================ sdPlayerStatEntry_Float::Write ================ */ bool sdPlayerStatEntry_Float::Write( sdNetStatKeyValList& kvList, int playerIndex, const char* name, bool failOnBlank ) const { if ( failOnBlank && values[ playerIndex ] == 0.f ) { return false; } idStrPool* globalKeys; idStrPool* globalValues; idDict::GetGlobalPools( globalKeys, globalValues ); sdNetStatKeyValue kv; kv.key = globalKeys->AllocString( name ); kv.type = type; kv.val.f = values[ playerIndex ]; kvList.Append( kv ); return true; } /* ================ sdPlayerStatEntry_Float::SetValue ================ */ void sdPlayerStatEntry_Float::SetValue( int playerIndex, const statValue_t& value ) { values[ playerIndex ] = value.GetFloat(); baseValues[ playerIndex ] = values[ playerIndex ]; } /* =============================================================================== sdPlayerStatEntry_Integer =============================================================================== */ /* ================ sdPlayerStatEntry_Integer::sdPlayerStatEntry_Integer ================ */ sdPlayerStatEntry_Integer::sdPlayerStatEntry_Integer( sdNetStatKeyValue::statValueType _type ) : sdPlayerStatEntry( _type ) { assert( _type == sdNetStatKeyValue::SVT_INT || _type == sdNetStatKeyValue::SVT_INT_MAX ); for ( int i = 0; i < MAX_CLIENTS; i++ ) { Clear( i ); } } /* ================ sdPlayerStatEntry_Integer::Clear ================ */ void sdPlayerStatEntry_Integer::Clear( int playerIndex ) { values[ playerIndex ] = 0; baseValues[ playerIndex ] = 0; } /* ================ sdPlayerStatEntry_Integer::IncreaseValue ================ */ void sdPlayerStatEntry_Integer::IncreaseValue( int playerIndex, const statValue_t& value ) { if ( gameLocal.rules->IsGameOn() ) { values[ playerIndex ] += value.GetInt(); } } /* ================ sdPlayerStatEntry_Integer::Display ================ */ void sdPlayerStatEntry_Integer::Display( const char* name ) const { gameLocal.Printf( "%s\n", name ); for ( int i = 0; i < MAX_CLIENTS; i++ ) { gameLocal.Printf( "%i ", values[ i ] ); } for ( int i = 0; i < MAX_CLIENTS; i++ ) { gameLocal.Printf( "%i ", baseValues[ i ] ); } gameLocal.Printf( "\n" ); } /* ================ sdPlayerStatEntry_Integer::Write ================ */ void sdPlayerStatEntry_Integer::Write( idFile* fp, int playerIndex, const char* name ) const { fp->WriteFloatString( "%s: %i\n", name, values[ playerIndex ] ); } /* ================ sdPlayerStatEntry_Integer::Write ================ */ bool sdPlayerStatEntry_Integer::Write( sdNetStatKeyValList& kvList, int playerIndex, const char* name, bool failOnBlank ) const { if ( failOnBlank && values[ playerIndex ] == 0 ) { return false; } idStrPool* globalKeys; idStrPool* globalValues; idDict::GetGlobalPools( globalKeys, globalValues ); sdNetStatKeyValue kv; kv.key = globalKeys->AllocString( name ); kv.type = type; kv.val.i = values[ playerIndex ]; kvList.Append( kv ); return true; } /* ================ sdPlayerStatEntry_Integer::SetValue ================ */ void sdPlayerStatEntry_Integer::SetValue( int playerIndex, const statValue_t& value ) { values[ playerIndex ] = value.GetInt(); baseValues[ playerIndex ] = values[ playerIndex ]; } /* =============================================================================== sdStatsCommand_Request =============================================================================== */ /* ================ sdStatsCommand_Request::Run ================ */ bool sdStatsCommand_Request::Run( sdStatsTracker& tracker, const idCmdArgs& args ) { return tracker.StartStatsRequest(); } /* =============================================================================== sdStatsCommand_Get =============================================================================== */ /* ================ sdStatsCommand_Get::Run ================ */ bool sdStatsCommand_Get::Run( sdStatsTracker& tracker, const idCmdArgs& args ) { if ( args.Argc() < 3 ) { gameLocal.Printf( "Insufficient Arguments\n" ); return false; } const char* name = args.Argv( 2 ); statHandle_t handle = tracker.GetStat( name ); if ( !handle.IsValid() ) { gameLocal.Printf( "Failed To Find Stat '%s'\n", name ); return false; } gameLocal.Printf( "Found Stat Handle %i\n", handle ); return true; } /* =============================================================================== sdStatsCommand_Display =============================================================================== */ /* ================ sdStatsCommand_Display::Run ================ */ bool sdStatsCommand_Display::Run( sdStatsTracker& tracker, const idCmdArgs& args ) { if ( args.Argc() < 3 ) { gameLocal.Printf( "Insufficient Arguments\n" ); return false; } const char* name = args.Argv( 2 ); statHandle_t handle = tracker.GetStat( name ); if ( !handle.IsValid() ) { gameLocal.Printf( "Failed To Find Stat '%s'\n", name ); return false; } tracker.DisplayStat( handle ); return true; } /* ================ sdStatsCommand_Display::CommandCompletion ================ */ void sdStatsCommand_Display::CommandCompletion( sdStatsTracker& tracker, const idCmdArgs& args, argCompletionCallback_t callback ) { for ( int i = 0; i < tracker.GetNumStats(); i++ ) { callback( va( "%s %s %s", args.Argv( 0 ), args.Argv( 1 ), tracker.GetStatName( i ) ) ); } } /* =============================================================================== sdStatsCommand_ClearUserStats =============================================================================== */ /* ================ sdStatsCommand_ClearUserStats::Run ================ */ bool sdStatsCommand_ClearUserStats::Run( sdStatsTracker& tracker, const idCmdArgs& args ) { sdNetUser* activeUser = networkService->GetActiveUser(); if ( activeUser == NULL ) { gameLocal.Printf( "No Active User\n" ); return false; } sdStatsTracker::ClearLocalUserStats( activeUser ); return true; } /* =============================================================================== sdStatsTracker =============================================================================== */ idHashMap< sdStatsCommand* > sdStatsTracker::s_commands; /* ================ sdStatsTracker::sdStatsTracker ================ */ sdStatsTracker::sdStatsTracker( void ) { requestState = SR_EMPTY; requestTask = NULL; for ( int i = 0; i < MAX_CLIENTS; i++ ) { playerRequestState[ i ] = -1; } } /* ================ sdStatsTracker::sdStatsTracker ================ */ sdStatsTracker::~sdStatsTracker( void ) { stats.DeleteContents( true ); statHash.Clear(); } /* ================ sdStatsTracker::Init ================ */ void sdStatsTracker::Init( void ) { s_commands.Set( "request", new sdStatsCommand_Request() ); s_commands.Set( "get", new sdStatsCommand_Get() ); s_commands.Set( "display", new sdStatsCommand_Display() ); s_commands.Set( "clearUserStats", new sdStatsCommand_ClearUserStats() ); } /* ================ sdStatsTracker::GetStat ================ */ statHandle_t sdStatsTracker::GetStat( const char* name ) const { int hashKey = idStr::Hash( name ); for ( int index = statHash.GetFirst( hashKey ); index != idHashIndex::NULL_INDEX; index = statHash.GetNext( index ) ) { if ( idStr::Icmp( stats[ index ]->GetName(), name ) != 0 ) { continue; } return index; } return statHandle_t(); } /* ================ sdStatsTracker::AllocStat ================ */ statHandle_t sdStatsTracker::AllocStat( const char* name, sdNetStatKeyValue::statValueType type ) { int hashKey = idStr::Hash( name ); for ( int index = statHash.GetFirst( hashKey ); index != idHashIndex::NULL_INDEX; index = statHash.GetNext( index ) ) { if ( idStr::Icmp( stats[ index ]->GetName(), name ) != 0 ) { continue; } if ( stats[ index ]->GetEntry()->GetType() != type ) { gameLocal.Error( "sdStatsTracker::AllocStat Type Mismatch ( '%d' v. '%d' ) On Re-Allocation of '%s'", stats[ index ]->GetEntry()->GetType(), type, name ); } return index; } statHandle_t handle = stats.Num(); sdPlayerStatEntry* stat = NULL; switch ( type ) { case sdNetStatKeyValue::SVT_INT: case sdNetStatKeyValue::SVT_INT_MAX: stat = new sdPlayerStatEntry_Integer( type ); break; case sdNetStatKeyValue::SVT_FLOAT: case sdNetStatKeyValue::SVT_FLOAT_MAX: stat = new sdPlayerStatEntry_Float( type ); break; } if ( stat == NULL ) { gameLocal.Error( "sdStatsTracker::AllocStat Failed to Alloc Stat '%s' of type '%d'", name, type ); } sdStatEntry* entry = new sdStatEntry( name, stat ); stats.Alloc() = entry; statHash.Add( hashKey, handle ); return handle; } /* ================ sdStatsTracker::Clear ================ */ void sdStatsTracker::Clear( void ) { gameLocal.Printf( "All Stats Cleared\n" ); for ( int i = 0; i < stats.Num(); i++ ) { for ( int p = 0; p < MAX_CLIENTS; p++ ) { stats[ i ]->Clear( p ); } } } /* ================ sdStatsTracker::Clear ================ */ void sdStatsTracker::Clear( int playerIndex ) { assert( playerIndex >= 0 && playerIndex < MAX_CLIENTS ); for ( int i = 0; i < stats.Num(); i++ ) { stats[ i ]->Clear( playerIndex ); } } /* ================ sdStatsTracker::DisplayStat ================ */ void sdStatsTracker::DisplayStat( statHandle_t handle ) const { assert( handle.IsValid() ); stats[ handle ]->Display(); } /* ================ sdStatsTracker::GetStat ================ */ sdPlayerStatEntry* sdStatsTracker::GetStat( statHandle_t handle ) const { if ( !handle.IsValid() ) { return NULL; } return stats[ handle ]->GetEntry(); } /* ================ sdStatsTracker::HandleCommand ================ */ void sdStatsTracker::HandleCommand( const idCmdArgs& args ) { if ( args.Argc() < 2 ) { gameLocal.Printf( "Insufficient Arguments\n" ); return; } sdStatsCommand** command = NULL; if ( !s_commands.Get( args.Argv( 1 ), &command ) ) { gameLocal.Printf( "Unknown Command '%s'\n", args.Argv( 1 ) ); return; } if ( !( *command )->Run( sdGlobalStatsTracker::GetInstance(), args ) ) { gameLocal.Printf( "Failed to Complete Command Properly\n" ); return; } } /* ================ sdStatsTracker::CommandCompletion ================ */ void sdStatsTracker::CommandCompletion( const idCmdArgs& args, argCompletionCallback_t callback ) { const char* cmd = args.Argv( 1 ); for ( int i = 0; i < s_commands.Num(); i++ ) { const char* name = s_commands.GetKeyList()[ i ].c_str(); if ( idStr::Icmp( cmd, name ) != 0 ) { continue; } s_commands.GetValList()[ i ]->CommandCompletion( sdGlobalStatsTracker::GetInstance(), args, callback ); } int cmdLength = idStr::Length( cmd ); for ( int i = 0; i < s_commands.Num(); i++ ) { const char* name = s_commands.GetKeyList()[ i ].c_str(); if ( idStr::Icmpn( cmd, name, cmdLength ) != 0 ) { continue; } callback( va( "%s %s", args.Argv( 0 ), name ) ); } } idCVar g_writeStats( "g_writeStats", "0", CVAR_BOOL | CVAR_GAME | CVAR_NOCHEAT, "write stats txt files at the end of maps" ); /* ================ sdStatsTracker::Write ================ */ void sdStatsTracker::Write( int playerIndex, const char* name ) { if ( g_writeStats.GetBool() ) { idStr temp = name; temp.CleanFilename(); idFile* fp = NULL; for ( int i = 1; i < 9999; i++ ) { const char* fileName = va( "/stats/%s_%i.txt", temp.c_str(), i ); fp = fileSystem->OpenFileRead( fileName, "fs_userpath" ); if ( fp != NULL ) { fileSystem->CloseFile( fp ); continue; } fp = fileSystem->OpenFileWrite( fileName ); break; } if ( fp != NULL ) { for ( int i = 0; i < stats.Num(); i++ ) { stats[ i ]->Write( fp, playerIndex ); } fileSystem->CloseFile( fp ); } } idPlayer* localPlayer = gameLocal.GetLocalPlayer(); if ( gameLocal.isServer && localPlayer != NULL ) { sdNetUser* activeUser = networkService->GetActiveUser(); if ( activeUser != NULL ) { WriteLocalUserStats( activeUser, playerIndex ); } } else { #if !defined( SD_DEMO_BUILD ) if ( networkService->GetDedicatedServerState() == sdNetService::DS_ONLINE ) { sdNetClientId netClientId; networkSystem->ServerGetClientNetId( playerIndex, netClientId ); if ( netClientId.IsValid() ) { sdNetStatKeyValList kvList; for ( int i = 0; i < stats.Num(); i++ ) { stats[ i ]->Write( kvList, playerIndex ); } networkService->GetStatsManager().WriteDictionary( netClientId, kvList ); } } #endif /* !SD_DEMO_BUILD */ } } /* ================ sdStatsTracker::Restore ================ */ void sdStatsTracker::Restore( int playerIndex ) { #if !defined( SD_DEMO_BUILD ) if ( networkService->GetDedicatedServerState() == sdNetService::DS_ONLINE ) { sdNetClientId netClientId; networkSystem->ServerGetClientNetId( playerIndex, netClientId ); if ( netClientId.IsValid() ) { sdNetStatKeyValList kvList; networkService->GetStatsManager().ReadCachedDictionary( netClientId, kvList ); for ( int i = 0; i < kvList.Num(); i++ ) { if ( kvList[ i ].key == NULL ) { continue; } const char* statName = kvList[ i ].key->c_str(); statHandle_t handle = GetStat( statName ); if ( !handle.IsValid() ) { continue; } sdPlayerStatEntry* entry = GetStat( handle ); switch ( kvList[ i ].type ) { case sdNetStatKeyValue::SVT_INT: case sdNetStatKeyValue::SVT_INT_MAX: entry->SetValue( playerIndex, kvList[ i ].val.i ); break; case sdNetStatKeyValue::SVT_FLOAT_MAX: case sdNetStatKeyValue::SVT_FLOAT: entry->SetValue( playerIndex, kvList[ i ].val.f ); break; } } } } #endif /* !SD_DEMO_BUILD */ } /* ================ sdStatsTracker::SetStatBaseLine ================ */ void sdStatsTracker::SetStatBaseLine( int playerIndex ) { for ( int i = 0; i < stats.Num(); i++ ) { stats[ i ]->GetEntry()->SaveBaseLine( playerIndex ); } } /* ================ sdStatsTracker::ProcessLocalStats ================ */ void sdStatsTracker::ProcessLocalStats( int playerIndex ) { sdNetStatKeyValList list; for ( int i = 0; i < stats.Num(); i++ ) { stats[ i ]->Write( list, playerIndex ); } OnServerStatsRequestMessage( list ); playerRequestState[ playerIndex ] = -1; OnServerStatsRequestCompleted(); } /* ================ sdStatsTracker::ProcessRemoteStats ================ */ void sdStatsTracker::ProcessRemoteStats( int playerIndex ) { const int MAX_SINGLE_SEND_COUNT = 30; int numLeft = stats.Num() - playerRequestState[ playerIndex ]; int numSend = Min( numLeft, MAX_SINGLE_SEND_COUNT ); sdNetStatKeyValList list; int start = playerRequestState[ playerIndex ]; int nextStatToWrite = start; for ( int i = 0; start + i < stats.Num() && list.Num() < numSend; i++ ) { int index = start + i; stats[ index ]->Write( list, playerIndex, true ); nextStatToWrite = index + 1; } sdReliableServerMessage msg( GAME_RELIABLE_SMESSAGE_STATSMESSAGE ); msg.WriteLong( list.Num() ); for ( int i = 0; i < list.Num(); i++ ) { msg.WriteString( list[ i ].key->c_str() ); msg.WriteByte( list[ i ].type ); switch ( list[ i ].type ) { case sdNetStatKeyValue::SVT_INT: case sdNetStatKeyValue::SVT_INT_MAX: msg.WriteLong( list[ i ].val.i ); break; case sdNetStatKeyValue::SVT_FLOAT: case sdNetStatKeyValue::SVT_FLOAT_MAX: msg.WriteFloat( list[ i ].val.f ); break; } } msg.Send( sdReliableMessageClientInfo( playerIndex ) ); playerRequestState[ playerIndex ] = nextStatToWrite; if ( playerRequestState[ playerIndex ] == stats.Num() ) { sdReliableServerMessage finishMsg( GAME_RELIABLE_SMESSAGE_STATSFINIHED ); finishMsg.WriteBool( true ); finishMsg.Send( sdReliableMessageClientInfo( playerIndex ) ); playerRequestState[ playerIndex ] = -1; } } /* ================ sdStatsTracker::UpdateStatsRequests ================ */ void sdStatsTracker::UpdateStatsRequests( void ) { UpdateStatsRequest(); if ( !gameLocal.isClient ) { for ( int i = 0; i < MAX_CLIENTS; i++ ) { if ( playerRequestState[ i ] == -1 || playerRequestWaiting[ i ] ) { continue; } idPlayer* player = gameLocal.GetClient( i ); if ( player == NULL ) { playerRequestState[ i ] = -1; continue; } if ( gameLocal.IsLocalPlayer( player ) ) { ProcessLocalStats( i ); } else { ProcessRemoteStats( i ); } } } } /* ================ sdStatsTracker::AcknowledgeStatsReponse ================ */ void sdStatsTracker::AcknowledgeStatsReponse( int playerIndex ) { playerRequestWaiting[ playerIndex ] = false; } /* ================ sdStatsTracker::AddStatsRequest ================ */ void sdStatsTracker::AddStatsRequest( int playerIndex ) { if ( playerRequestState[ playerIndex ] != -1 ) { // already doing stuff return; } playerRequestState[ playerIndex ] = 0; playerRequestWaiting[ playerIndex ] = false; } /* ================ sdStatsTracker::CancelStatsRequest ================ */ void sdStatsTracker::CancelStatsRequest( int playerIndex ) { if ( playerRequestState[ playerIndex ] == -1 ) { // not doing anything anyway return; } playerRequestState[ playerIndex ] = -1; idPlayer* player = gameLocal.GetClient( playerIndex ); if ( player != NULL ) { if ( gameLocal.IsLocalPlayer( player ) ) { OnServerStatsRequestCancelled(); } else { sdReliableServerMessage msg( GAME_RELIABLE_SMESSAGE_STATSFINIHED ); msg.WriteBool( false ); msg.Send( sdReliableMessageClientInfo( playerIndex ) ); } } } /* ================ sdStatsTracker::OnServerStatsRequestCancelled ================ */ void sdStatsTracker::OnServerStatsRequestCancelled( void ) { if ( requestState != SR_REQUESTING ) { return; } if ( requestTask != NULL ) { networkService->FreeTask( requestTask ); requestTask = NULL; } requestedStatsValid = false; serverStatsValid = false; requestState = SR_FAILED; } /* ================ sdStatsTracker::OnServerStatsRequestCompleted ================ */ void sdStatsTracker::OnServerStatsRequestCompleted( void ) { if ( requestState != SR_REQUESTING ) { return; } serverStatsValid = true; if ( requestedStatsValid ) { OnStatsRequestFinished(); } } /* ================ sdStatsTracker::OnServerStatsRequestMessage ================ */ void sdStatsTracker::OnServerStatsRequestMessage( const idBitMsg& msg ) { idStrPool* globalKeys; idStrPool* globalValues; idDict::GetGlobalPools( globalKeys, globalValues ); char buffer[ 2048 ]; sdNetStatKeyValList list; int numEntries = msg.ReadLong(); for ( int i = 0; i < numEntries; i++ ) { msg.ReadString( buffer, sizeof( buffer ) ); sdNetStatKeyValue kv; kv.type = ( sdNetStatKeyValue::statValueType )msg.ReadByte(); kv.key = globalKeys->AllocString( buffer ); switch ( kv.type ) { case sdNetStatKeyValue::SVT_FLOAT: case sdNetStatKeyValue::SVT_FLOAT_MAX: kv.val.f = msg.ReadFloat(); break; case sdNetStatKeyValue::SVT_INT: case sdNetStatKeyValue::SVT_INT_MAX: kv.val.i = msg.ReadLong(); break; } list.Append( kv ); } OnServerStatsRequestMessage( list ); sdReliableClientMessage response( GAME_RELIABLE_CMESSAGE_ACKNOWLEDGESTATS ); response.Send(); } /* ================ sdStatsTracker::OnServerStatsRequestMessage ================ */ void sdStatsTracker::OnServerStatsRequestMessage( const sdNetStatKeyValList& list ) { serverStats.Append( list ); } /* ================ sdStatsTracker::ClearLocalUserStats ================ */ void sdStatsTracker::ClearLocalUserStats( sdNetUser* activeUser ) { assert( activeUser != NULL ); idDict& info = activeUser->GetProfile().GetProperties(); idStr statPrefix = "localstat_"; idStr statTypePrefix = "localstattype_"; const idKeyValue* kv; while ( ( kv = info.MatchPrefix( statPrefix.c_str(), NULL ) ) != NULL ) { info.Delete( kv->GetKey().c_str() ); } while ( ( kv = info.MatchPrefix( statTypePrefix.c_str(), NULL ) ) != NULL ) { info.Delete( kv->GetKey().c_str() ); } activeUser->Save( sdNetUser::SI_PROFILE ); } /* ================ sdStatsTracker::ReadLocalUserStats ================ */ void sdStatsTracker::ReadLocalUserStats( sdNetUser* activeUser, sdNetStatKeyValList& list ) { list.SetNum( 0, false ); const idDict& info = activeUser->GetProfile().GetProperties(); idStrPool* globalKeys; idStrPool* globalValues; idDict::GetGlobalPools( globalKeys, globalValues ); idStr statPrefix = "localstat_"; idStr statTypePrefix = "localstattype_"; const idKeyValue* kv = NULL; while ( ( kv = info.MatchPrefix( statPrefix.c_str(), kv ) ) != NULL ) { sdNetStatKeyValue statKV; const char* key = kv->GetKey().c_str() + statPrefix.Length(); statKV.type = ( sdNetStatKeyValue::statValueType )info.GetInt( va( "%s%s", statTypePrefix.c_str(), key ) ); statKV.key = globalKeys->AllocString( key ); switch ( statKV.type ) { case sdNetStatKeyValue::SVT_FLOAT: case sdNetStatKeyValue::SVT_FLOAT_MAX: statKV.val.f = info.GetFloat( kv->GetKey() ); break; case sdNetStatKeyValue::SVT_INT: case sdNetStatKeyValue::SVT_INT_MAX: statKV.val.i = info.GetInt( kv->GetKey() ); break; default: continue; } list.Append( statKV ); } } /* ================ sdStatsTracker::WriteLocalUserStats ================ */ void sdStatsTracker::WriteLocalUserStats( sdNetUser* activeUser, int playerIndex ) { idDict& info = activeUser->GetProfile().GetProperties(); idStr statPrefix = "localstat_"; idStr statTypePrefix = "localstattype_"; sdNetStatKeyValList kvList; for ( int i = 0; i < stats.Num(); i++ ) { stats[ i ]->Write( kvList, playerIndex ); if ( kvList.Num() == 0 ) { continue; } sdNetStatKeyValue& stat = kvList[ 0 ]; info.SetInt( va( "%s%s", statTypePrefix.c_str(), stats[ i ]->GetName() ), stat.type ); const char* key = va( "%s%s", statPrefix.c_str(), stats[ i ]->GetName() ); switch ( stat.type ) { case sdNetStatKeyValue::SVT_FLOAT: { float old = info.GetFloat( key ); info.SetFloat( key, old + stat.val.f ); break; } case sdNetStatKeyValue::SVT_FLOAT_MAX: { float old = info.GetFloat( key ); if ( stat.val.f > old ) { info.SetFloat( key, stat.val.f ); } break; } case sdNetStatKeyValue::SVT_INT: { int old = info.GetInt( key ); info.SetInt( key, old + stat.val.i ); break; } case sdNetStatKeyValue::SVT_INT_MAX: { int old = info.GetInt( key ); if ( stat.val.i > old ) { info.SetInt( key, stat.val.i ); } break; } } kvList.SetNum( 0, false ); } activeUser->Save( sdNetUser::SI_PROFILE ); } idCVar g_useSimpleStats( "g_useSimpleStats", "0", CVAR_BOOL | CVAR_GAME, "only look up local server stats" ); /* ================ sdStatsTracker::StartStatsRequest ================ */ bool sdStatsTracker::StartStatsRequest( bool globalOnly ) { if ( requestState == SR_REQUESTING ) { return false; } requestState = SR_FAILED; idPlayer* localPlayer = gameLocal.GetLocalPlayer(); if ( localPlayer == NULL && !globalOnly ) { return false; } assert( requestTask == NULL ); serverStatsValid = globalOnly ? true : false; serverStats.SetNum( 0, false ); serverStatsHash.Clear(); requestedStatsValid = false; requestedStats.SetNum( 0, false ); requestedStatsHash.Clear(); if ( g_useSimpleStats.GetBool() && !globalOnly ) { requestedStatsValid = true; } else { sdNetUser* activeUser = networkService->GetActiveUser(); if ( activeUser != NULL ) { if ( gameLocal.isServer ) { ReadLocalUserStats( activeUser, requestedStats ); requestedStatsValid = true; } #if !defined( SD_DEMO_BUILD ) else { sdNetClientId clientId; activeUser->GetAccount().GetNetClientId( clientId ); if ( clientId.IsValid() ) { requestTask = networkService->GetStatsManager().ReadDictionary( clientId, requestedStats ); } } #endif /* !SD_DEMO_BUILD */ } if ( !requestedStatsValid ) { if ( requestTask == NULL ) { if ( !globalOnly ) { requestedStatsValid = true; // Gordon: just default to getting "server" stats if this stuff fails } else { return false; } } } } requestState = SR_REQUESTING; if ( !globalOnly ) { if ( gameLocal.isClient ) { sdReliableClientMessage msg( GAME_RELIABLE_CMESSAGE_REQUESTSTATS ); msg.Send(); } else { AddStatsRequest( localPlayer->entityNumber ); } } return true; } /* ================ sdStatsTracker::UpdateStatsRequest ================ */ void sdStatsTracker::UpdateStatsRequest( void ) { if ( requestState != SR_REQUESTING ) { return; } if ( requestTask != NULL ) { sdNetTask::taskStatus_e taskStatus = requestTask->GetState(); if ( taskStatus == sdNetTask::TS_DONE ) { sdNetErrorCode_e errorCode = requestTask->GetErrorCode(); if ( errorCode == SDNET_NO_ERROR ) { requestedStatsValid = true; } else { requestState = SR_FAILED; } networkService->FreeTask( requestTask ); requestTask = NULL; } } if ( requestedStatsValid && serverStatsValid ) { OnStatsRequestFinished(); } } /* ================ sdStatsTracker::OnStatsRequestFinished ================ */ void sdStatsTracker::OnStatsRequestFinished( void ) { requestState = SR_COMPLETED; completeStats.Clear(); completeStatsHash.Clear(); completeStats.SetNum( requestedStats.Num() ); for( int i = 0; i < requestedStats.Num(); i++ ) { completeStats[ i ] = requestedStats[ i ]; completeStatsHash.Add( completeStatsHash.GenerateKey( completeStats[ i ].key->c_str(), false ), i ); requestedStatsHash.Add( requestedStatsHash.GenerateKey( completeStats[ i ].key->c_str(), false ), i ); } for ( int i = 0; i < serverStats.Num(); i++ ) { sdNetStatKeyValue* kv = GetLocalStat( serverStats[ i ].key->c_str() ); if( kv != NULL ) { // Gordon: We can't tell if the data from DW is a max stat or not, so we rely on the server information // The only case where this matters on the client is this loop, so it doesn't matter anyway switch( serverStats[ i ].type ) { case sdNetStatKeyValue::SVT_FLOAT: kv->val.f += serverStats[ i ].val.f; break; case sdNetStatKeyValue::SVT_INT: kv->val.i += serverStats[ i ].val.i; break; case sdNetStatKeyValue::SVT_FLOAT_MAX: if ( serverStats[ i ].val.f > kv->val.f ) { kv->val.f = serverStats[ i ].val.f; } break; case sdNetStatKeyValue::SVT_INT_MAX: if ( serverStats[ i ].val.i > kv->val.i ) { kv->val.i = serverStats[ i ].val.i; } break; } } else { completeStatsHash.Add( completeStatsHash.GenerateKey( serverStats[ i ].key->c_str(), false ), completeStats.Append( serverStats[ i ] ) ); } serverStatsHash.Add( serverStatsHash.GenerateKey( serverStats[ i ].key->c_str(), false ), i ); } lifeStatsData.SetNum( 0, false ); lifeStatsDataHash.Clear(); if ( serverStatsValid && requestedStatsValid ) { const idList< lifeStat_t >& stats = gameLocal.lifeStats; for ( int j = 0; j < stats.Num(); j++ ) { if ( GetLifeStatData( stats[ j ].stat.c_str() ) != NULL ) { continue; } const char* lifeStatName = va( "lifestat_%s", stats[ j ].stat.c_str() ); lifeStatsData_t data; data.index = j; const sdNetStatKeyValue* netKV = GetNetStat( lifeStatName ); const sdNetStatKeyValue* serverKV = GetServerStat( lifeStatName ); if ( netKV == NULL && serverKV == NULL ) { continue; } sdNetStatKeyValue::statValueType type = netKV != NULL ? netKV->type : serverKV->type; switch ( type ) { case sdNetStatKeyValue::SVT_INT: case sdNetStatKeyValue::SVT_INT_MAX: data.oldValue = netKV != NULL ? netKV->val.i : 0; data.newValue = serverKV != NULL ? serverKV->val.i : 0; break; case sdNetStatKeyValue::SVT_FLOAT: case sdNetStatKeyValue::SVT_FLOAT_MAX: data.oldValue = netKV != NULL ? netKV->val.f : 0.f; data.newValue = serverKV != NULL ? serverKV->val.f : 0.f; break; } lifeStatsDataHash.Add( lifeStatsDataHash.GenerateKey( stats[ j ].stat.c_str(), false ), lifeStatsData.Append( data ) ); } } /* for ( int i = 0; i < lifeStatsData.Num(); i++ ) { const char* name = lifeStatsData[ i ].GetName(); switch ( lifeStatsData[ i ].oldValue.GetType() ) { case sdNetStatKeyValue::SVT_INT: gameLocal.Printf( "Life Stat: %s Old: %d New: %d\n", name, lifeStatsData[ i ].oldValue.GetInt(), lifeStatsData[ i ].newValue.GetInt() ); break; case sdNetStatKeyValue::SVT_FLOAT: gameLocal.Printf( "Life Stat: %s Old: %f New: %f\n", name, lifeStatsData[ i ].oldValue.GetFloat(), lifeStatsData[ i ].newValue.GetFloat() ); break; } }*/ } /* ============ sdStatsTracker::GetLocalStat ============ */ const sdNetStatKeyValue* sdStatsTracker::GetLocalStat( const char* name ) const { int hashKey = idStr::Hash( name ); for ( int index = completeStatsHash.GetFirst( hashKey ); index != idHashIndex::NULL_INDEX; index = completeStatsHash.GetNext( index ) ) { if ( idStr::Icmp( completeStats[ index ].key->c_str(), name ) != 0 ) { continue; } return &completeStats[ index ]; } return NULL; } /* ============ sdStatsTracker::GetLocalStat ============ */ sdNetStatKeyValue* sdStatsTracker::GetLocalStat( const char* name ) { int hashKey = idStr::Hash( name ); for ( int index = completeStatsHash.GetFirst( hashKey ); index != idHashIndex::NULL_INDEX; index = completeStatsHash.GetNext( index ) ) { if ( idStr::Icmp( completeStats[ index ].key->c_str(), name ) != 0 ) { continue; } return &completeStats[ index ]; } return NULL; } /* ============ sdStatsTracker::GetNetStat ============ */ const sdNetStatKeyValue* sdStatsTracker::GetNetStat( const char* name ) const { int hashKey = idStr::Hash( name ); for ( int index = requestedStatsHash.GetFirst( hashKey ); index != idHashIndex::NULL_INDEX; index = requestedStatsHash.GetNext( index ) ) { if ( idStr::Icmp( requestedStats[ index ].key->c_str(), name ) != 0 ) { continue; } return &requestedStats[ index ]; } return NULL; } /* ============ sdStatsTracker::GetServerStat ============ */ const sdNetStatKeyValue* sdStatsTracker::GetServerStat( const char* name ) const { int hashKey = idStr::Hash( name ); for ( int index = serverStatsHash.GetFirst( hashKey ); index != idHashIndex::NULL_INDEX; index = serverStatsHash.GetNext( index ) ) { if ( idStr::Icmp( serverStats[ index ].key->c_str(), name ) != 0 ) { continue; } return &serverStats[ index ]; } return NULL; } /* ============ sdStatsTracker::GetLifeStatData ============ */ sdStatsTracker::lifeStatsData_t* sdStatsTracker::GetLifeStatData( const char* name ) { int hashKey = idStr::Hash( name ); for ( int index = lifeStatsDataHash.GetFirst( hashKey ); index != idHashIndex::NULL_INDEX; index = lifeStatsDataHash.GetNext( index ) ) { if ( idStr::Icmp( lifeStatsData[ index ].GetName(), name ) != 0 ) { continue; } return &lifeStatsData[ index ]; } return NULL; } static const float STATS_EPSILON = 0.01f; class sdSortPercentageImprovement { public: int operator()( const sdStatsTracker::lifeStatsData_t* lhs, const sdStatsTracker::lifeStatsData_t* rhs ) const { int lhsPercent = CalcPercentage( lhs ); int rhsPercent = CalcPercentage( rhs ); return idMath::FtoiFast( lhsPercent - rhsPercent ); } private: static int CalcPercentage( const sdStatsTracker::lifeStatsData_t* data ) { switch( data->newValue.GetType() ) { case sdNetStatKeyValue::SVT_FLOAT_MAX: { float delta = ( data->newValue.GetFloat() - data->oldValue.GetFloat() ); if( idMath::Fabs( data->oldValue.GetFloat() ) >= STATS_EPSILON ) { return ( delta / data->oldValue.GetFloat() ) * 100; } } break; case sdNetStatKeyValue::SVT_INT_MAX: { float delta = ( data->newValue.GetInt() - data->oldValue.GetInt() ); if( idMath::Abs( data->oldValue.GetInt() ) > 0 ) { return ( delta / data->oldValue.GetInt() ) * 100; } } break; } return 0; } }; /* ============ sdStatsTracker::GetTopLifeStats ============ */ void sdStatsTracker::GetTopLifeStats( idList< const lifeStatsData_t* >& improved, idList< const lifeStatsData_t* >& unchanged, idList< const lifeStatsData_t* >& worse ) const { improved.SetNum( 0, false ); unchanged.SetNum( 0, false ); worse.SetNum( 0, false ); for( int i = 0; i < lifeStatsData.Num(); i++ ) { const lifeStatsData_t& data = lifeStatsData[ i ]; switch ( data.newValue.GetType() ) { case sdNetStatKeyValue::SVT_FLOAT: case sdNetStatKeyValue::SVT_FLOAT_MAX: if ( data.newValue.GetFloat() > data.oldValue.GetFloat() ) { improved.Append( &data ); } else if( idMath::Fabs( data.newValue.GetFloat() - data.oldValue.GetFloat() ) < STATS_EPSILON ) { unchanged.Append( &data ); } else { worse.Append( &data ); } break; case sdNetStatKeyValue::SVT_INT: case sdNetStatKeyValue::SVT_INT_MAX: if ( data.newValue.GetInt() > data.oldValue.GetInt() ) { improved.Append( &data ); } else if( data.newValue.GetInt() == data.oldValue.GetInt() ) { unchanged.Append( &data ); } else { worse.Append( &data ); } break; default: assert( false ); break; } } sdQuickSort( improved.Begin(), improved.End(), sdSortPercentageImprovement() ); }