//---------------------------------------------------------------- // StatManager.cpp // // Copyright 2002-2004 Raven Software //---------------------------------------------------------------- #include "../../../idlib/precompiled.h" #pragma hdrstop #include "../../Game_local.h" #include "StatManager.h" // TTimo - *????????* #include rvStatManager statManagerLocal; rvStatManager* statManager = &statManagerLocal; comboKillState_t rvStatManager::comboKillState[ MAX_CLIENTS ] = { CKS_NONE }; int rvStatManager::lastRailShot[ MAX_CLIENTS ] = { -2 }; int rvStatManager::lastRailShotHits[ MAX_CLIENTS ] = { 0 }; inGameAwardInfo_t inGameAwardInfo[ IGA_NUM_AWARDS ] = { //IGA_INVALID { NULL }, //IGA_CAPTURE { "capture" }, //IGA_HUMILIATION { "humiliation" }, //IGA_IMPRESSIVE { "impressive" }, //IGA_EXCELLENT { "excellent" }, //IGA_ASSIST { "assist" }, //IGA_DEFENSE { "defense" }, //IGA_COMBO_KILL { "combo_kill" }, //IGA_RAMPAGE { "rampage" }, //IGA_HOLY_SHIT { "holy_shit" } }; // RAVEN BEGIN // rhummer: localized these strings. endGameAwardInfo_t endGameAwardInfo[ EGA_NUM_AWARDS ] = { //EGA_INVALID { NULL }, //EGA_LEMMING { "#str_107260" }, //EGA_RAIL_MASTER { "#str_107261" }, //EGA_ROCKET_SAUCE { "#str_107262" }, //EGA_BRAWLER { "#str_107263" }, //EGA_SNIPER { "#str_107264" }, //EGA_CRITICAL_FAILURE { "#str_107265" }, //EGA_TEAM_PLAYER { "#str_107266" }, //EGA_ACCURACY { "#str_107267" }, //EGA_FRAGS { "#str_107268" }, //EGA_PERFECT { "#str_107269" } }; // RAVEN END void showStats_f( const idCmdArgs &args ) { statManager->DebugPrint(); } /* =============================================================================== rvStatAllocator Handles allocating stat events =============================================================================== */ void *rvStatAllocator::GetBlock( size_t blockSize, int* blockNumOut /* = NULL */ ) { // if not enough bytes for this allocation, get a new block if ( GetBytesLeftInBlock() < blockSize ) { // recycle & reuse a block currentBlock++; placeInBlock = 0; if( currentBlock >= MAX_BLOCKS ) { currentBlock = 0; if( statManager->GetStat( 0 ) && gameLocal.isMultiplayer ) { // int delta = gameLocal.time - statManager->GetStat( 0 )->GetTimeStamp(); // gameLocal.Printf( "rvStatAllocator::GetBlock() - Tracked %g seconds of stats before recycle\n", delta / 1000.0f ); } } // gameLocal.Printf( "rvStatAllocator::GetBlock() - Using block %d\n", currentBlock ); // statManager->DebugPrint(); // int numFreed = statManager->FreeEvents( currentBlock ); // statManager->DebugPrint(); // gameLocal.Printf( "rvStatAllocator::GetBlock() - stat manager freed %d events which used block %d\n", numFreed, currentBlock ); } // if this fires our objects are too large or our alloc size is too small assert( GetBytesLeftInBlock() >= blockSize ); byte *newBlock = blocks + ( BLOCK_SIZE * currentBlock ) + placeInBlock; placeInBlock += blockSize; totalAllocations++; totalBytesUsed += blockSize; if( blockNumOut ) { // return the current block number if requested (*blockNumOut) = currentBlock; } return newBlock; } void rvStatAllocator::Reset() { currentBlock = 0; placeInBlock = 0; totalBytesUsed = 0; totalAllocations = 0; totalBytesAllocated = 0; for ( int i = 0; i < ST_COUNT; i++ ) { allocationsByType[ i ] = 0; } } rvStatAllocator::rvStatAllocator() { Reset(); } void rvStatAllocator::Report() { // shouchard: for debugging and tuning only common->Printf( "rvStatAllocator: dump of usage stats\n" ); common->Printf( "\t%d total bytes handed out in %d requests\n", GetTotalBytesUsed(), GetTotalAllocations() ); common->Printf( "\tbegin game: %3d; end game: %3d\n", GetAllocationsByType( ST_BEGIN_GAME ), GetAllocationsByType( ST_END_GAME ) ); common->Printf( "\tplayer hit: %3d; player kill: %3d\n", GetAllocationsByType( ST_HIT ), GetAllocationsByType( ST_KILL ) ); common->Printf( "\tplayer death: %3d;\n", GetAllocationsByType( ST_DEATH ) ); common->Printf( "\tdamage dealt: %3d; damage taken: %3d\n", GetAllocationsByType( ST_DAMAGE_DEALT ), GetAllocationsByType( ST_DAMAGE_TAKEN ) ); common->Printf( "\tstat team: %3d\n", GetAllocationsByType( ST_STAT_TEAM ) ); common->Printf( "\tflag capture: %3d;\n", GetAllocationsByType( ST_CTF_FLAG_CAPTURE ) ), common->Printf( "\tflag drop: %3d; flag return: %3d\n", GetAllocationsByType( ST_CTF_FLAG_DROP ), GetAllocationsByType( ST_CTF_FLAG_RETURN ) ); } // object allocators #if defined(_INLINEDEBUGMEMORY) // Because we need inplace new. #undef new #define new new #endif // _INLINEDEBUGMEMORY rvStatBeginGame *rvStatAllocator::AllocStatBeginGame( int t, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatBeginGame ), blockNumOut ); assert( newBlock ); rvStatBeginGame *stat = new( newBlock ) rvStatBeginGame( t ); allocationsByType[ ST_BEGIN_GAME ]++; return stat; } rvStatEndGame *rvStatAllocator::AllocStatEndGame( int t, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatEndGame ), blockNumOut ); assert( newBlock ); rvStatEndGame *stat = new( newBlock ) rvStatEndGame( t ); allocationsByType[ ST_END_GAME ]++; return stat; } rvStatClientConnect *rvStatAllocator::AllocStatClientConnect( int t, int client, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatClientConnect ), blockNumOut ); assert( newBlock ); rvStatClientConnect *stat = new( newBlock ) rvStatClientConnect( t, client ); allocationsByType[ ST_CLIENT_CONNECT ]++; return stat; } rvStatHit *rvStatAllocator::AllocStatHit( int t, int p, int v, int w, bool countForAccuracy, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatHit ), blockNumOut ); assert( newBlock ); rvStatHit *stat = new( newBlock ) rvStatHit( t, p, v, w, countForAccuracy ); allocationsByType[ ST_HIT ]++; return stat; } rvStatKill *rvStatAllocator::AllocStatKill( int t, int p, int v, bool g, int mod, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatKill ), blockNumOut ); assert( newBlock ); rvStatKill *stat = new( newBlock ) rvStatKill( t, p, v, g, mod ); allocationsByType[ ST_KILL ]++; return stat; } rvStatDeath *rvStatAllocator::AllocStatDeath( int t, int p, int mod, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatDeath ), blockNumOut ); assert( newBlock ); rvStatDeath *stat = new( newBlock ) rvStatDeath( t, p, mod ); allocationsByType[ ST_DEATH ]++; return stat; } rvStatDamageDealt *rvStatAllocator::AllocStatDamageDealt( int t, int p, int w, int d, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatDamageDealt ), blockNumOut ); assert( newBlock ); rvStatDamageDealt *stat = new( newBlock ) rvStatDamageDealt( t, p, w, d ); allocationsByType[ ST_DAMAGE_DEALT ]++; return stat; } rvStatDamageTaken *rvStatAllocator::AllocStatDamageTaken( int t, int p, int w, int d, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatDamageTaken ), blockNumOut ); assert( newBlock ); rvStatDamageTaken *stat = new( newBlock ) rvStatDamageTaken( t, p, w, d ); allocationsByType[ ST_DAMAGE_TAKEN ]++; return stat; } rvStatFlagDrop *rvStatAllocator::AllocStatFlagDrop( int t, int p, int a, int tm, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatFlagDrop ), blockNumOut ); assert( newBlock ); rvStatFlagDrop *stat = new( newBlock ) rvStatFlagDrop( t, p, a, tm ); allocationsByType[ ST_CTF_FLAG_DROP ]++; return stat; } rvStatFlagReturn *rvStatAllocator::AllocStatFlagReturn( int t, int p, int tm, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatFlagReturn ), blockNumOut ); assert( newBlock ); rvStatFlagReturn *stat = new( newBlock ) rvStatFlagReturn( t, p, tm ); allocationsByType[ ST_CTF_FLAG_CAPTURE ]++; return stat; } rvStatFlagCapture *rvStatAllocator::AllocStatFlagCapture( int t, int p, int f, int tm, int* blockNumOut /* = NULL */ ) { void *newBlock = GetBlock( sizeof( rvStatFlagCapture ), blockNumOut ); assert( newBlock ); rvStatFlagCapture *stat = new( newBlock ) rvStatFlagCapture( t, p, f, tm ); allocationsByType[ ST_CTF_FLAG_RETURN ]++; return stat; } #if defined(_INLINEDEBUGMEMORY) // Because we need inplace new. #undef new #define new ID_DEBUG_NEW #endif // _INLINEDEBUGMEMORY /* =============================================================================== rvStatManager Stores game statistic events in statQueue =============================================================================== */ // shouchard: stat manager start with 1 meg; we'll tune as we get better data rvStatManager::rvStatManager() { memset( localInGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); inGameAwardHudTime = 0; } void rvStatManager::Init( void ) { Shutdown(); statQueue.Clear(); awardQueue.Clear(); statQueue.SetGranularity( 1024 ); endGameSetup = false; cmdSystem->AddCommand( "ShowInGameStats", showStats_f, CMD_FL_SYSTEM, "show in game stats." ); memset( localInGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); inGameAwardHudTime = 0; } void rvStatManager::Shutdown( void ) { statAllocator.Report(); statAllocator.Reset(); statQueue.Clear(); awardQueue.Clear(); for( int i = 0; i < MAX_CLIENTS; i++ ) { playerStats[ i ] = rvPlayerStat(); } for ( int q = 0; q < MAX_CLIENTS; ++q ) { comboKillState[ q ] = CKS_NONE; lastRailShot[ q ] = -2; lastRailShotHits[ q ] = 0; } } void rvStatManager::BeginGame( void ) { int blockNum; rvStatBeginGame* stat = statAllocator.AllocStatBeginGame( gameLocal.time, &blockNum ); statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); endGameSetup = false; #if ID_TRAFFICSTATS startTime = gameLocal.time; networkSystem->GetTrafficStats( startSent, startPacketsSent, startReceived, startPacketsReceived ); #endif for ( int q = 0; q < MAX_CLIENTS; ++q ) { comboKillState[ q ] = CKS_NONE; lastRailShot[ q ] = -2; lastRailShotHits[ q ] = 0; } } void rvStatManager::EndGame( void ) { int blockNum; rvStatEndGame* stat = statAllocator.AllocStatEndGame( gameLocal.time, &blockNum ); statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); CalculateEndGameStats(); awardQueue.Clear(); #if ID_TRAFFICSTATS int sent, packetsSent, received, packetsReceived, time; networkSystem->GetTrafficStats( sent, packetsSent, received, packetsReceived ); sent -= startSent; packetsSent -= startPacketsSent; received -= startReceived; packetsReceived -= startPacketsReceived; time = gameLocal.time - startTime; common->Printf( "EndGame. bytes sent: %d packets sent: %d bytes received: %d packets received: %d\n", sent, packetsSent, received, packetsReceived ); // compute averages, including packet overhead // adjust the UDP overhead, may depend on your TCP stack implementation ( 42 comes from ethereal analysis of the traffic ) sent += packetsSent * 42; received += packetsReceived * 42; float sentBps, recvBps; sentBps = (float)( sent ) * 1000.0f / time; recvBps = (float)( received ) * 1000.0f / time; common->Printf( "avg sent %g B/s, received %g B/s\n", sentBps, recvBps ); #endif } void rvStatManager::ClientConnect( int clientNum ) { // push a client connected event into the queue so we don't get confused with old // events detailing the previous owner of this clientNum int blockNum; rvStatClientConnect* stat = statAllocator.AllocStatClientConnect( gameLocal.time, clientNum, &blockNum ); statQueue.Append( rvPair( (rvStat*)stat, blockNum ) ); } void rvStatManager::ClientDisconnect( int clientNum ) { // re-init player stats playerStats[ clientNum ] = rvPlayerStat(); } void rvStatManager::Kill( const idPlayer* victim, const idEntity* killer, int methodOfDeath ) { int deathBlock, killBlock; rvStatDeath* statDeath = statAllocator.AllocStatDeath( gameLocal.time, victim->entityNumber, methodOfDeath, &deathBlock ); statQueue.Append( rvPair( (rvStat*)statDeath, deathBlock ) ); if( killer && killer->IsType( idPlayer::GetClassType() ) ) { rvStatKill* statKill = statAllocator.AllocStatKill( gameLocal.time, killer->entityNumber, victim->entityNumber, victim->IsGibbed(), methodOfDeath, &killBlock ); statQueue.Append( rvPair( (rvStat*)statKill, killBlock ) ); } else if( !killer ) { // basically a suicide rvStatKill* statKill = statAllocator.AllocStatKill( gameLocal.time, victim->entityNumber, victim->entityNumber, victim->IsGibbed(), methodOfDeath, &killBlock ); statQueue.Append( rvPair( (rvStat*)statKill, killBlock ) ); } } void rvStatManager::FlagCaptured( const idPlayer* player, int flagTeam ) { int blockNum; rvStatFlagCapture* stat = statAllocator.AllocStatFlagCapture( gameLocal.time, player->entityNumber, flagTeam, player->team, &blockNum ); statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); } void rvStatManager::WeaponFired( const idPlayer* player, int weapon, int num ) { playerStats[ player->entityNumber ].weaponShots[ weapon ] += num; lastRailShotHits[ player->entityNumber ] = 0; comboKillState_t cks = comboKillState[ player->entityNumber ]; comboKillState[ player->entityNumber ] = CKS_NONE; if ( player->GetWeaponIndex( "weapon_rocketlauncher" ) == weapon ) { if ( cks == CKS_NONE ) { comboKillState[ player->entityNumber ] = CKS_ROCKET_FIRED; } } else if ( player->GetWeaponIndex( "weapon_railgun" ) == weapon ) { // apparently it processes hits before it does the fire.... if ( cks == CKS_RAIL_HIT ) { comboKillState[ player->entityNumber ] = CKS_RAIL_FIRED; } } } void rvStatManager::WeaponHit( const idActor* attacker, const idEntity* victim, int weapon, bool countForAccuracy ) { if( victim && attacker && ( victim == attacker || gameLocal.IsTeamGame( ) && victim->IsType( idPlayer::GetClassType( ) ) && attacker->IsType( idPlayer::GetClassType( ) ) && static_cast( victim )->team == static_cast( attacker )->team ) ) { return; } if( victim && victim != attacker ) { if( attacker->IsType( idPlayer::GetClassType() ) ) { // if attacker was a player, track hit and damage dealt int hitBlock; rvStatHit* statHit = statAllocator.AllocStatHit( gameLocal.time, attacker->entityNumber, victim->entityNumber, weapon, countForAccuracy, &hitBlock ); statQueue.Append( rvPair( (rvStat*)(statHit), hitBlock ) ); } } } //asalmon modified to work for single player stats on Xenon void rvStatManager::Damage( const idEntity* attacker, const idEntity* victim, int weapon, int damage ) { if( victim && attacker && ( victim == attacker || gameLocal.IsTeamGame( ) && victim->IsType( idPlayer::GetClassType( ) ) && attacker->IsType( idPlayer::GetClassType( ) ) && static_cast( victim )->team == static_cast( attacker )->team ) ) { return; } if(attacker) { if( attacker->IsType( idPlayer::GetClassType() ) ) { int damageBlock; rvStatDamageDealt* statDamage = statAllocator.AllocStatDamageDealt( gameLocal.time, attacker->entityNumber, weapon, damage, &damageBlock ); statQueue.Append( rvPair( (rvStat*)(statDamage), damageBlock ) ); } } if(victim) { if( victim->IsType( idPlayer::GetClassType() ) ) { // if victim was a player, track damage taken int blockNum; rvStatDamageTaken* stat = statAllocator.AllocStatDamageTaken( gameLocal.time, victim->entityNumber, weapon, damage, &blockNum ); statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); } } } void rvStatManager::FlagDropped( const idPlayer* player, const idEntity* attacker ) { int blockNum; rvStatFlagDrop* stat = statAllocator.AllocStatFlagDrop( gameLocal.time, player->entityNumber, attacker->entityNumber, player->team, &blockNum ); statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); } void rvStatManager::FlagReturned( const idPlayer* player ) { int blockNum; rvStatFlagReturn* stat = statAllocator.AllocStatFlagReturn( gameLocal.time, player->entityNumber, player->team, &blockNum ); statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); } void rvStatManager::DebugPrint( void ) { if( !gameLocal.isMultiplayer ) { return; } //gameLocal.Printf( "Begin statistics debug dump:\n" ); //gameLocal.Printf( "Statistics queue:\n" ); for( int i = 0; i < Min( statQueue.Num(), 50 ); i++ ) { gameLocal.Printf( "{%d, %d} ", statQueue[i].First()->GetType(), statQueue[i].First()->GetTimeStamp() ); } gameLocal.Printf("\n"); //gameLocal.Printf( "In-game statistics\n\n" ); //for( int i = 0; i < inGameStats.Num(); i++ ) { // gameLocal.Printf( "\t%d - %s:\n", i, statIndex->index[ i ].GetName().c_str() ); // gameLocal.Printf( "\t\tKills: %d\n", inGameStats[i].kills ); // gameLocal.Printf( "\t\tDeaths: %d\n", inGameStats[i].deaths ); // gameLocal.Printf( "\t\tFlag Caps: %d\n", inGameStats[i].flagCaptures ); // for( int j = 0; j < MAX_WEAPONS; j++ ) { // gameLocal.Printf( "\t\tWeapon %d hits: %d\n\t\tWeapon %d shots: %d\n", j, inGameStats[i].weaponHits[j], j, inGameStats[i].weaponShots[j] ); // } //} } int rvStatManager::FreeEvents( int blockNum ) { int blockStart = -1; int blockEnd = -1; for( int i = 0; i < statQueue.Num(); i++ ) { if( blockStart == -1 && statQueue[ i ].Second() == blockNum ) { blockStart = i; } else if( blockStart != -1 && statQueue[ i ].Second() != blockNum ) { blockEnd = i; break; } } if( blockStart == -1 || blockEnd == -1 ) { // rjohnson: commented out warning - I take it this is not a bad message? // gameLocal.Warning( "rvStatManager::FreeEvents() - Could not find events with block num '%d'\n", blockNum ); return 0; } statQueue.RemoveRange( blockStart, blockEnd - 1 ); return (blockEnd - blockStart); } void rvStatManager::SendInGameAward( inGameAward_t award, int clientNum ) { assert( gameLocal.isServer ); idBitMsg msg; byte msgBuf[1024]; msg.Init( msgBuf, sizeof( msgBuf ) ); msg.WriteByte( GAME_RELIABLE_MESSAGE_INGAMEAWARD ); msg.WriteByte( award ); msg.WriteByte( clientNum ); networkSystem->ServerSendReliableMessage( -1, msg ); if( gameLocal.isListenServer ) { msg.ReadByte(); ReceiveInGameAward( msg ); } } void rvStatManager::ReceiveInGameAward( const idBitMsg& msg ) { assert( gameLocal.isClient || gameLocal.isListenServer ); int numAwards = 0; inGameAward_t award = (inGameAward_t)msg.ReadByte(); int client = msg.ReadByte(); // display award on hud idPlayer* player = gameLocal.GetLocalPlayer(); idPlayer* remote = gameLocal.GetClientByNum(client); bool justSound = false; if ( client != gameLocal.localClientNum ) { justSound = true; } if ( ( gameLocal.time - inGameAwardHudTime ) < 3000 || awardQueue.Num() > 0 ) { if ( gameLocal.GetDemoFollowClient() == client || ( player != NULL && remote != NULL && player->GetInstance() == remote->GetInstance() ) ) { rvPair awardPair(award, justSound); awardQueue.StackAdd(awardPair); return; } } if( client == gameLocal.localClientNum ) { // don't count awards during warmup if( !player || (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP && (gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( player->GetArena() ).GetState() != AS_WARMUP )) ) { localInGameAwards[ award ]++; numAwards = localInGameAwards[ award ]; } else { numAwards = 1; } if( player && player->mphud ) { player->mphud->HandleNamedEvent( "clearIGA" ); player->mphud->SetStateInt( "ig_awards", idMath::ClampInt( 0, 10, numAwards ) ); player->mphud->SetStateString( "ig_award", va( "gfx/mp/awards/%s", inGameAwardInfo[ award ].name ) ); if( numAwards < 10 ) { player->mphud->SetStateString( "ig_award_num", ""); for( int i = 0; i < idMath::ClampInt( 0, 10, numAwards ); i++ ) { player->mphud->SetStateInt( va( "ig_awards_%d", i + 1 ), 1 ); } } else { player->mphud->SetStateInt( "ig_award_num", numAwards ); player->mphud->SetStateInt( "ig_awards", 1 ); player->mphud->SetStateInt( va( "ig_awards_%d", 1 ), 1 ); } //inGameAwardHudTime = gameLocal.time; player->mphud->HandleNamedEvent( "giveIGA" ); player->mphud->StateChanged( gameLocal.time ); } if ( player ) { player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); } } else if ( player && remote && ( player->GetInstance() == remote->GetInstance() ) && (award == IGA_HOLY_SHIT || award == IGA_CAPTURE || award == IGA_HUMILIATION )) { player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); } inGameAwardHudTime = gameLocal.time; idPlayer* awardee = gameLocal.GetClientByNum( client ); if ( awardee ) { if ( player && player->GetInstance() == awardee->GetInstance() ) { iconManager->AddIcon( client, va( "mtr_award_%s", inGameAwardInfo[ award ].name ) ); } } } void rvStatManager::CheckAwardQueue() { if(((gameLocal.time - inGameAwardHudTime) < 3000 || awardQueue.Num() == 0)) { return; } idPlayer* player = gameLocal.GetLocalPlayer(); int award = awardQueue.StackTop().First(); bool justSound = awardQueue.StackTop().Second(); awardQueue.StackPop(); if ( player ) { if(!justSound || award == IGA_HOLY_SHIT || award == IGA_CAPTURE) { player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); } } if( player && player->mphud && !justSound) { player->mphud->HandleNamedEvent( "clearIGA" ); localInGameAwards[ award ]++; player->mphud->SetStateInt( "ig_awards", idMath::ClampInt( 0, 10, localInGameAwards[ award ] ) ); player->mphud->SetStateString( "ig_award", va( "gfx/mp/awards/%s", inGameAwardInfo[ award ].name ) ); if(localInGameAwards[ award ] < 10) { player->mphud->SetStateString( "ig_award_num", ""); for( int i = 0; i < idMath::ClampInt( 0, 10, localInGameAwards[ award ] ); i++ ) { player->mphud->SetStateInt( va( "ig_awards_%d", i + 1 ), 1 ); } } else { player->mphud->SetStateInt( "ig_award_num", localInGameAwards[ award ]); player->mphud->SetStateInt( "ig_awards", 1 ); player->mphud->SetStateInt( va( "ig_awards_%d", 1 ), 1 ); } inGameAwardHudTime = gameLocal.time; player->mphud->HandleNamedEvent( "giveIGA" ); player->mphud->StateChanged( gameLocal.time ); } } void rvStatManager::GivePlayerCashForAward( idPlayer* player, inGameAward_t award ) { if( !player ) return; if( !gameLocal.isMultiplayer ) return; if( !gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) return; mpGameState_t mpGameState = gameLocal.mpGame.GetGameState()->GetMPGameState(); if( mpGameState != GAMEON && mpGameState != SUDDENDEATH ) return; const char* awardCashValueName = NULL; switch( award ) { case IGA_CAPTURE: awardCashValueName = "playerCashAward_mpAward_capture"; break; case IGA_HUMILIATION: awardCashValueName = "playerCashAward_mpAward_humiliation"; break; case IGA_IMPRESSIVE: awardCashValueName = "playerCashAward_mpAward_impressive"; break; case IGA_EXCELLENT: awardCashValueName = "playerCashAward_mpAward_excellent"; break; case IGA_ASSIST: awardCashValueName = "playerCashAward_mpAward_assist"; break; case IGA_DEFENSE: awardCashValueName = "playerCashAward_mpAward_defense"; break; case IGA_COMBO_KILL: awardCashValueName = "playerCashAward_mpAward_combo_kill"; break; case IGA_RAMPAGE: awardCashValueName = "playerCashAward_mpAward_rampage"; break; case IGA_HOLY_SHIT: awardCashValueName = "playerCashAward_mpAward_holy_shit"; break; } if( awardCashValueName ) { player->GiveCash( (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( awardCashValueName, 0 ) ); } } void rvStatManager::GiveInGameAward( inGameAward_t award, int clientNum ) { idPlayer* player = (idPlayer*)gameLocal.entities[ clientNum ]; if( gameLocal.isMultiplayer ) { // show in-game awards during warmup, but don't actually let players accumulate them if( !player || (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP && (gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( player->GetArena() ).GetState() != AS_WARMUP )) ) { playerStats[ clientNum ].inGameAwards[ award ]++; GivePlayerCashForAward( player, award ); } SendInGameAward( award, clientNum ); } } void rvStatManager::SetupStatWindow( idUserInterface* statHud ) { statWindow.SetupStatWindow( statHud ); } void rvStatManager::SelectStatWindow( int selectionIndex, int selectionTeam ) { statWindow.SelectPlayer( statWindow.ClientNumFromSelection( selectionIndex, selectionTeam ) ); } int rvStatManager::GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ) { return statWindow.GetSelectedClientNum( selectionIndexOut, selectionTeamOut ); } void rvStatManager::UpdateInGameHud( idUserInterface* statHud, bool visible ) { idPlayer* player = NULL; if( gameLocal.GetLocalPlayer() ) { player = gameLocal.GetLocalPlayer(); player->GetHud()->SetStateInt( "stat_visible", visible? 1 : 0); } if( !visible ) { statHud->SetStateInt( "stat_visible", 0 ); return; } else { statHud->SetStateInt( "stat_visible", 1 ); } if( player ) { statWindow.SetupStatWindow( statHud, player->spectating ); } } void rvStatManager::SendStat( int toClient, int statClient ) { if( statClient < 0 || statClient >= MAX_CLIENTS ) { gameLocal.Warning( "rvStatManager::SendStat() - Stats requested for invalid client num '%d'\n", statClient ); return; } idBitMsg outMsg; byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STAT ); outMsg.WriteByte( statClient ); playerStats[ statClient ].PackStats( outMsg ); networkSystem->ServerSendReliableMessage( toClient, outMsg ); } void rvStatManager::ReceiveStat( const idBitMsg& msg ) { //asalmon: added because this is used to restore single player stats from saves on Xbox 360 if(gameLocal.IsMultiplayer()) { assert( gameLocal.isClient ); } int client = msg.ReadByte(); playerStats[ client ].UnpackStats( msg ); playerStats[ client ].lastUpdateTime = gameLocal.time; // display the updated stat if(gameLocal.IsMultiplayer()) { statWindow.SelectPlayer( client ); } } void rvStatManager::SendAllStats( int clientNum, bool full ) { assert( gameLocal.isServer ); idBitMsg outMsg; byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_ALL_STATS ); assert( MAX_CLIENTS <= 32 ); unsigned sentClients = 0; for(int i=0; i < MAX_CLIENTS; i++) { if ( gameLocal.entities[ i ] ) { sentClients |= 1 << i; } } outMsg.WriteBits( sentClients, MAX_CLIENTS ); for(int i=0; i < MAX_CLIENTS; i++) { if ( sentClients & ( 1 << i ) ) { playerStats[ i ].PackStats( outMsg ); } } networkSystem->ServerSendReliableMessage( clientNum, outMsg ); //common->Printf("SENT ALL STATS %i\n", Sys_Milliseconds()); } void rvStatManager::ReceiveAllStats( const idBitMsg& msg ) { assert( gameLocal.isClient ); assert( MAX_CLIENTS <= 32 ); unsigned sentClients = msg.ReadBits( MAX_CLIENTS ); for(int i=0; i < MAX_CLIENTS; i++) { if ( sentClients & ( 1 << i ) ) { playerStats[ i ].UnpackStats( msg ); } else { playerStats[ i ].Clear(); } playerStats[ i ].lastUpdateTime = gameLocal.time; } if ( gameLocal.mpGame.GetGameState()->GetMPGameState() == GAMEREVIEW ) { gameLocal.mpGame.ShowStatSummary(); } } void rvStatManager::ClearStats( void ) { // clear connected stats for( int i = 0; i < MAX_CLIENTS; i++ ) { playerStats[ i ] = rvPlayerStat(); } for ( int q = 0; q < MAX_CLIENTS; ++q ) { lastRailShot[ q ] = -2; lastRailShotHits[ q ] = 0; } #ifdef _XENON lastFullUpdate = -50000; #endif } void rvStatManager::CalculateEndGameStats( void ) { int maxKills = idMath::INT_MIN; int maxKillPlayer = -1; int maxSuicides = idMath::INT_MIN; int maxSuicidesPlayer = -1; int maxGauntletKills = idMath::INT_MIN; int maxGauntletKillsPlayer = -1; float maxDamageKillsRatio = 0.0; int maxDamageKillsRatioPlayer = -1; //Dump the stats to a log file idFile *log = NULL; if(cvarSystem->GetCVarBool("com_logMPStats")) { log = fileSystem->OpenFileAppend("StatisticsLog.txt"); } idStr toFile; if ( !log ) { // common->Warning("Statistics log will not be written\n"); } else { struct tm *newtime; time_t aclock; time( &aclock ); newtime = localtime( &aclock ); toFile = va("Match on map %s played on %s\n", gameLocal.GetMapName(), asctime( newtime )); log->Write(toFile.c_str(), toFile.Length()); } for( int i = 0; i < MAX_CLIENTS; i++ ) { if( !gameLocal.entities[ i ] ) { continue; } if(log) { toFile = va("Statistics for %s\n", gameLocal.userInfo[ i ].GetString("ui_name")); log->Write(toFile.c_str(), toFile.Length()); toFile = va("Kills: %i Deaths: %i Suicides: %i\n", playerStats[ i ].kills, playerStats[ i ].deaths, playerStats[ i ].suicides); log->Write(toFile.c_str(), toFile.Length()); } gameLocal.Printf( "Calculating stats for client %d (%s)\n", i, gameLocal.userInfo[ i ].GetString("ui_name") ); // overall accuracy award and sniper accuracy award idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; int railgunIndex = player->GetWeaponIndex( "weapon_railgun" ); int rocketIndex = player->GetWeaponIndex( "weapon_rocketlauncher" ); float accuracyAverage = 0.0f; int numAccuracies = 0; for( int j = 0; j < MAX_WEAPONS; j++ ) { if( playerStats[ i ].weaponShots[ j ] == 0 ) { if(log) { if(player->GetWeaponDef(j)) { toFile = va("%s not used\n", common->GetLocalizedString(player->GetWeaponDef(j)->dict.GetString("inv_name"))); log->Write(toFile.c_str(), toFile.Length()); } } continue; } float weaponAccuracy = (float)playerStats[ i ].weaponHits[ j ] / (float)playerStats[ i ].weaponShots[ j ]; if(log) { if(player->GetWeaponDef(j)) { toFile = va("%s: %i%%\n", common->GetLocalizedString(player->GetWeaponDef(j)->dict.GetString("inv_name")), (int)(((float)playerStats[ i ].weaponHits[ j ] / (float)playerStats[ i ].weaponShots[ j ]) * 100.0f)); log->Write(toFile.c_str(), toFile.Length()); } } if( j == railgunIndex ) { // sniper award if( weaponAccuracy >= 0.9f && playerStats[ i ].weaponShots[ railgunIndex ] >= 10 ) { playerStats[ i ].endGameAwards.Append( EGA_SNIPER ); } } accuracyAverage += weaponAccuracy; numAccuracies++; } if ( numAccuracies && ( accuracyAverage / (float)numAccuracies >= 0.5f ) ) { playerStats[ i ].endGameAwards.Append( EGA_ACCURACY ); } // rail master award if ( playerStats[ i ].kills && ( (float)playerStats[ i ].weaponKills[ railgunIndex ] / (float)playerStats[ i ].kills >= 0.8f ) ) { playerStats[ i ].endGameAwards.Append( EGA_RAIL_MASTER ); } // rocket sauce award if ( playerStats[ i ].kills && ( (float)playerStats[ i ].weaponKills[ rocketIndex ] / (float)playerStats[ i ].kills >= 0.8f ) ) { playerStats[ i ].endGameAwards.Append( EGA_ROCKET_SAUCE ); } // critical failure award if( playerStats[ i ].kills == 0 ) { playerStats[ i ].endGameAwards.Append( EGA_CRITICAL_FAILURE ); } // frags award //asalmon: Made the limit more reasonable for the shorter time limits and less players of Xenon #ifdef _XENON if( playerStats[ i ].kills >= 50 ) { playerStats[ i ].endGameAwards.Append( EGA_FRAGS ); } #else if( playerStats[ i ].kills >= 100 ) { playerStats[ i ].endGameAwards.Append( EGA_FRAGS ); } #endif if( playerStats[ i ].kills > maxKills ) { maxKills = playerStats[ i ].kills; maxKillPlayer = i; } if( playerStats[ i ].suicides > maxSuicides ) { maxSuicides = playerStats[ i ].suicides; maxSuicidesPlayer = i; } if( playerStats[ i ].weaponKills[ player->GetWeaponIndex( "weapon_gauntlet" ) ] > maxGauntletKills ) { maxGauntletKills = playerStats[ i ].weaponKills[ player->GetWeaponIndex( "weapon_gauntlet" ) ]; maxGauntletKillsPlayer = i; } //asalmon: Calculate the damage ratio: if(playerStats[i].kills > 0) { playerStats[i].damageRatio = playerStats[i].damageGiven / playerStats[i].kills; } else { playerStats[i].damageRatio = playerStats[i].damageGiven; } if( playerStats[ i ].damageRatio > maxDamageKillsRatio ) { maxDamageKillsRatio = playerStats[ i ].damageRatio; maxDamageKillsRatioPlayer = i; } if(log) { toFile = "\n"; log->Write(toFile.c_str(), toFile.Length()); } //asalmon: hack to test certain achievement awards. #ifdef _XENON if(cvarSystem->GetCVarInteger("si_overrideFrags")) { common->Printf("Overriding Frags to: %i for player %i\n", cvarSystem->GetCVarInteger("si_overrideFrags"), i); playerStats[i].kills = cvarSystem->GetCVarInteger("si_overrideFrags"); } #endif } // Perfect award if( maxKillPlayer >= 0 && playerStats[ maxKillPlayer ].deaths == 0 ) { idPlayer* player = (idPlayer*)gameLocal.entities[ maxKillPlayer ]; if( !gameLocal.IsTeamGame() || ( gameLocal.IsTeamGame() && player && gameLocal.mpGame.TeamLeader() == player->team ) ) { playerStats[ maxKillPlayer ].endGameAwards.Append( EGA_PERFECT ); } } // Lemming award if( maxSuicidesPlayer >= 0 && maxSuicides >= 5 ) { playerStats[ maxSuicidesPlayer ].endGameAwards.Append( EGA_LEMMING ); } // Brawler award if( maxGauntletKillsPlayer >= 0 && maxGauntletKills >= 3 ) { playerStats[ maxGauntletKillsPlayer ].endGameAwards.Append( EGA_BRAWLER ); } // Team player award if( maxDamageKillsRatioPlayer >= 0 && maxDamageKillsRatio > 500 ) { playerStats[ maxDamageKillsRatioPlayer ].endGameAwards.Append( EGA_TEAM_PLAYER ); } if(log) { toFile = "\n"; log->Write(toFile.c_str(), toFile.Length()); log->Flush(); fileSystem->CloseFile(log); } } void rvStatManager::GetAccuracyLeaders( int accuracyLeaders[ MAX_WEAPONS ] ) { memset( accuracyLeaders, -1, sizeof( int ) * MAX_WEAPONS ); for( int i = 0; i < MAX_CLIENTS; i++ ) { if( gameLocal.entities[ i ] == NULL ) { continue; } rvPlayerStat* playerStats = GetPlayerStat( i ); for( int j = 0; j < MAX_WEAPONS; j++ ) { if( playerStats->weaponShots[ j ] == 0 ) { continue; } float playerAccuracy = (float)playerStats->weaponHits[ j ] / (float)playerStats->weaponShots[ j ]; float leaderAccuracy = -1.0f; if( accuracyLeaders[ j ] != -1 ) { rvPlayerStat* leaderStats = GetPlayerStat( accuracyLeaders[ j ] ); if( leaderStats->weaponShots[ j ] != 0 ) { leaderAccuracy = (float)leaderStats->weaponHits[ j ] / (float)leaderStats->weaponShots[ j ]; } } if( playerAccuracy > leaderAccuracy ) { accuracyLeaders[ j ] = i; } } } } int rvStatManager::DamageGiven( int playerNum, int lowerBound, int upperBound ) { if( playerNum < 0 || playerNum >= MAX_CLIENTS ) { return 0; } int damage = 0; for( int i = 0; i < statQueue.Num(); i++ ) { if( statQueue[ i ].First()->GetType() == ST_DAMAGE_DEALT ) { if( statQueue[ i ].First()->GetPlayerClientNum() == playerNum && (statQueue[ i ].First()->GetTimeStamp() > lowerBound && statQueue[ i ].First()->GetTimeStamp() < upperBound) ) { damage += static_cast(statQueue[ i ].First())->GetDamage(); } } } return damage; } int rvStatManager::DamageTaken( int playerNum, int lowerBound, int upperBound ) { if( playerNum < 0 || playerNum >= MAX_CLIENTS ) { return 0; } int damage = 0; for( int i = 0; i < statQueue.Num(); i++ ) { if( statQueue[ i ].First()->GetType() == ST_DAMAGE_TAKEN ) { if( statQueue[ i ].First()->GetPlayerClientNum() == playerNum && ( statQueue[ i ].First()->GetTimeStamp() > lowerBound && statQueue[ i ].First()->GetTimeStamp() < upperBound ) ) { damage += static_cast(statQueue[ i ].First())->GetDamage(); } } } return damage; } rvStat* rvStatManager::GetLastClientStat( int clientNum, statType_t type, int time ) { for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { if( statQueue[ i ].First()->GetTimeStamp() < time ) { return NULL; } if( statQueue[ i ].First()->GetType() == type ) { if( clientNum == -1 || statQueue[ i ].First()->GetPlayerClientNum() == clientNum ) { return statQueue[ i ].First(); } } } return NULL; } void rvStatManager::GetLastClientStats( int clientNum, statType_t type, int time, int num, rvStat** results ) { int numFound = 0; for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { if( statQueue[ i ].First()->GetTimeStamp() < time ) { return; } if( statQueue[ i ].First()->GetType() == type ) { if( clientNum == -1 || statQueue[ i ].First()->GetPlayerClientNum() == clientNum ) { results[ numFound++ ] = statQueue[ i ].First(); if( numFound >= num ) { return; } } } } return; } rvStatTeam* rvStatManager::GetLastTeamStat( int team, statType_t type, int time ) { assert( type > ST_STAT_TEAM ); for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { if( statQueue[ i ].First()->GetTimeStamp() < time ) { return NULL; } if( statQueue[ i ].First()->GetType() == type ) { if( ((rvStatTeam*)statQueue[ i ].First())->GetTeam() == team ) { return (rvStatTeam*)statQueue[ i ].First(); } } } return NULL; } void rvStatManager::SetupEndGameHud( idUserInterface* statHud ) { if( gameLocal.IsFlagGameType() ) { statHud->SetStateInt( "ctf_awards", 1 ); } else { statHud->SetStateInt( "ctf_awards", 0 ); } for( int i = 0; i < MAX_CLIENTS; i++ ) { idPlayer* p = gameLocal.mpGame.GetRankedPlayer( i ); if( p && gameLocal.mpGame.IsInGame( p->entityNumber ) ) { statHud->SetStateString( va( "player%d_name", i + 1 ), va( "%d. %s", i + 1, gameLocal.userInfo[ p->entityNumber ].GetString( "ui_name" ) ) ); statHud->SetStateString( va( "player%d_score", i + 1 ), va( "%d", gameLocal.mpGame.GetScore( i ) ) ); statHud->SetStateInt( va( "player%d_visible", i + 1 ), 1 ); statHud->SetStateInt( va( "player%d_team", i + 1 ), gameLocal.IsTeamGame() ? p->team : 0 ); } else { statHud->SetStateInt( va( "player%d_visible", i + 1 ), 0 ); } } statHud->HandleNamedEvent( "Setup" ); } rvPlayerStat* rvStatManager::GetPlayerStat( int clientNum ) { return &playerStats[ clientNum ]; } void rvStatManager::UpdateEndGameHud( idUserInterface* statHud, int clientNum ) { /*if( !endGameSetup ) { // no info yet SetupEndGameHud( statHud ); endGameSetup = true; } rvPlayerStat* clientStat = &(playerStats[ clientNum ]); statHud->HandleNamedEvent( "clear" ); statHud->SetStateString( "stat_name", gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ); // weapon accuracy for( int i = 0; i < MAX_WEAPONS; i++ ) { statHud->SetStateString( va( "stat_%d_pct", i ), va( "%d%%", (int)(clientStat->weaponAccuracy[ i ] * 100) ) ); } // in-game awards int igAwardCount[ IGA_NUM_AWARDS ]; memset( igAwardCount, 0, sizeof( int ) * IGA_NUM_AWARDS ); for( int i = 0; i < clientStat->inGameAwards.Num(); i++ ) { igAwardCount[ clientStat->inGameAwards[ i ] ]++; } for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { statHud->SetStateString( inGameAwardInfo[ i ].name, va( "%d", igAwardCount[ i ] ) ); } // end-game awards for( int i = 0; i < clientStat->endGameAwards.Num(); i++ ) { statHud->SetStateInt( va( "eg_award%d", i ), 1 ); statHud->SetStateString( va( "eg_award%d_text", i ), endGameAwardInfo[ clientStat->endGameAwards[ i ] ].name ); } // kills statHud->SetStateString( "stat_frags", va( "%d", clientStat->kills ) ); // deaths statHud->SetStateString( "stat_deaths", va( "%d", clientStat->deaths ) );*/ } /* =============================================================================== rvStatSummary Stores one player's summary information. Transmitted to clients for intermission summary screen. =============================================================================== */ rvPlayerStat::rvPlayerStat() { Clear(); } void rvPlayerStat::Clear( void ) { memset( weaponShots, 0, sizeof(int) * MAX_WEAPONS ); memset( weaponHits, 0, sizeof(int) * MAX_WEAPONS ); memset( weaponKills, 0, sizeof(int) * MAX_WEAPONS ); memset( inGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); kills = deaths = suicides = lastUpdateTime = damageTaken = damageGiven = 0; damageRatio = 0.0f; } void rvPlayerStat::PackStats( idBitMsg& msg ) { for( int i = 0; i < MAX_WEAPONS; i++ ) { msg.WriteShort( weaponShots[ i ] ); } for( int i = 0; i < MAX_WEAPONS; i++ ) { msg.WriteShort( weaponHits[ i ] ); } for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { msg.WriteByte( inGameAwards[ i ] ); } msg.WriteByte( endGameAwards.Num() ); for( int i = 0; i < endGameAwards.Num(); i++ ) { msg.WriteByte( endGameAwards[ i ] ); } msg.WriteBits( idMath::ClampInt( 0, MP_PLAYER_MAXDEATHS, deaths ), ASYNC_PLAYER_DEATH_BITS ); msg.WriteBits( idMath::ClampInt( 0, MP_PLAYER_MAXKILLS, kills ), ASYNC_PLAYER_KILL_BITS ); } void rvPlayerStat::UnpackStats( const idBitMsg& msg ) { for( int i = 0; i < MAX_WEAPONS; i++ ) { weaponShots[ i ] = msg.ReadShort(); } for( int i = 0; i < MAX_WEAPONS; i++ ) { weaponHits[ i ] = msg.ReadShort(); } for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { inGameAwards[ i ] = msg.ReadByte(); } endGameAwards.SetNum( msg.ReadByte() ); for( int i = 0; i < endGameAwards.Num(); i++ ) { endGameAwards[ i ] = (endGameAward_t)msg.ReadByte(); } deaths = msg.ReadBits( ASYNC_PLAYER_DEATH_BITS ); kills = msg.ReadBits( ASYNC_PLAYER_KILL_BITS ); }