1345 lines
41 KiB
C++
1345 lines
41 KiB
C++
//----------------------------------------------------------------
|
|
// StatManager.cpp
|
|
//
|
|
// Copyright 2002-2004 Raven Software
|
|
//----------------------------------------------------------------
|
|
|
|
#include "../../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "../../Game_local.h"
|
|
#include "StatManager.h"
|
|
|
|
// TTimo - *????????*
|
|
#include <new>
|
|
|
|
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*, int>( (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*, int>( (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*,int>( (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*, int>( (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*, int>( (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*, int>( (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*, int>( (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<const idPlayer*>( victim )->team == static_cast<const idPlayer*>( 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*, int>( (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<const idPlayer*>( victim )->team == static_cast<const idPlayer*>( 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*, int>( (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*, int>( (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*, int>( (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*, int>( (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<int,bool> 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<rvStatDamageDealt*>(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<rvStatDamageTaken*>(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 );
|
|
}
|