Quake 4 integrated with DoomRTX

This commit is contained in:
Justin Marshall
2026-05-09 22:10:40 -07:00
parent 8c4a087aa9
commit d37f87e493
436 changed files with 277587 additions and 10254 deletions
+331
View File
@@ -0,0 +1,331 @@
//----------------------------------------------------------------
// StatEvent.cpp
//
// Copyright 2002-2005 Raven Software
//----------------------------------------------------------------
#include "precompiled.h"
#pragma hdrstop
#include "../../Game_local.h"
#include "StatManager.h"
rvStatHit::rvStatHit( int t, int p, int v, int w, bool countForAccuracy ) : rvStat( t ) {
playerClientNum = p;
victimClientNum = v;
weapon = w;
trackAccuracy = countForAccuracy;
type = ST_HIT;
RegisterInGame( statManager->GetPlayerStats() );
}
void rvStatHit::RegisterInGame( rvPlayerStat* stats ) {
if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS && weapon >= 0 && weapon < MAX_WEAPONS && trackAccuracy ) {
stats[ playerClientNum ].weaponHits[ weapon ]++;
}
idPlayer* player = (idPlayer*)gameLocal.entities[ playerClientNum ];
if( !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", weapon ) ), "weapon_railgun" ) ) {
// Apparently it does the rail hit event before the rail fired event, so this is backwards for a reason
if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_ROCKET_HIT ) {
rvStatManager::comboKillState[ playerClientNum ] = CKS_RAIL_HIT;
}
if ( rvStatManager::lastRailShot[ playerClientNum ] < stats[ playerClientNum ].weaponShots[ weapon ] - 1 ) {
rvStatManager::lastRailShot[ playerClientNum ] = stats[ playerClientNum ].weaponShots[ weapon ];
} else {
if ( rvStatManager::lastRailShot[ playerClientNum ] >= stats[ playerClientNum ].weaponShots[ weapon ] - 1 ) {
if ( (rvStatManager::lastRailShotHits[ playerClientNum ] % 2) == 0 ) {
statManager->GiveInGameAward( IGA_IMPRESSIVE, playerClientNum );
}
++rvStatManager::lastRailShotHits[ playerClientNum ];
} else {
rvStatManager::lastRailShot[ playerClientNum ] = stats[ playerClientNum ].weaponShots[ weapon ];
}
}
/*
rvStatHit* lastHits[ 2 ] = { NULL, NULL };
statManager->GetLastClientStats( playerClientNum, ST_HIT, gameLocal.time - 8000, 2, (rvStat**)lastHits );
if( lastHits[0] ) {
if( !idStr::Icmp(player->spawnArgs.GetString( va( "def_weapon%d", ((rvStatHit*)lastHits[0])->weapon ) ), "weapon_railgun" ) ) {
//Check to make sure we don't chain impressive awards.
if(lastHits[1]
&& !idStr::Icmp(player->spawnArgs.GetString( va( "def_weapon%d", ((rvStatHit*)lastHits[1])->weapon ) ), "weapon_railgun" )
&& (lastHits[1]->GetTimeStamp() - lastHits[0]->GetTimeStamp()) < 4000) {
return;
}
if(gameLocal.time - lastHits[0]->GetTimeStamp() < 4000){
statManager->GiveInGameAward( IGA_IMPRESSIVE, playerClientNum );
}
}
}
*/
} else if( !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", weapon ) ), "weapon_rocketlauncher" ) ) {
if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_ROCKET_FIRED ) {
rvStatManager::comboKillState[ playerClientNum ] = CKS_ROCKET_HIT;
}
}
}
rvStatKill::rvStatKill( int t, int p, int v, bool g, int mod ) : rvStat( t ) {
type = ST_KILL;
playerClientNum = p;
victimClientNum = v;
gibbed = g;
methodOfDeath = mod;
RegisterInGame( statManager->GetPlayerStats() );
}
void rvStatKill::RegisterInGame( rvPlayerStat* stats ) {
if( playerClientNum < 0 || playerClientNum >= MAX_CLIENTS || victimClientNum < 0 || victimClientNum >= MAX_CLIENTS ) {
return;
}
idPlayer* player = (idPlayer*)gameLocal.entities[ playerClientNum ];
idPlayer* victim = (idPlayer*)gameLocal.entities[ victimClientNum ];
// no award processing for suicides
if( victimClientNum == playerClientNum ) {
stats[ playerClientNum ].suicides++;
return;
}
bool teamKill = false;
// don't track team kills
if( !gameLocal.IsTeamGame() || player->team != victim->team ) {
stats[ playerClientNum ].kills++;
if( methodOfDeath >= 0 && methodOfDeath < MAX_WEAPONS ) {
stats[ playerClientNum ].weaponKills[ methodOfDeath ]++;
}
}
if( gameLocal.IsTeamGame() && player->team == victim->team ) {
teamKill = true;
}
// check for humiliation award
if( !teamKill && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", methodOfDeath ) ), "weapon_gauntlet" ) ) {
statManager->GiveInGameAward( IGA_HUMILIATION, playerClientNum );
}
rvStatKill* lastKills[ 2 ] = { NULL, NULL };
statManager->GetLastClientStats( playerClientNum, ST_KILL, gameLocal.time - 5000, 2, (rvStat**)lastKills );
// check for excellent award
if( !teamKill && lastKills[ 0 ] && lastKills[ 0 ]->GetTimeStamp() >= (gameLocal.time - 2000) && lastKills[ 0 ]->victimClientNum != playerClientNum && victimClientNum != playerClientNum ) {
statManager->GiveInGameAward( IGA_EXCELLENT, playerClientNum );
}
// check for rampage award
if( !teamKill && ( gibbed && victimClientNum != playerClientNum ) &&
( lastKills[ 0 ] && lastKills[ 0 ]->gibbed && lastKills[ 0 ]->victimClientNum != playerClientNum ) &&
( lastKills[ 1 ] && lastKills[ 1 ]->gibbed && lastKills[ 1 ]->victimClientNum != playerClientNum ) ) {
statManager->GiveInGameAward( IGA_RAMPAGE, playerClientNum );
}
// check for combo kill award
if( victimClientNum != playerClientNum && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", methodOfDeath ) ), "weapon_railgun" ) ) {
// the rail killing shot is the last hit, so look for the one past that
rvStatHit* lastHits[ 2 ] = { NULL, NULL };
statManager->GetLastClientStats( playerClientNum, ST_HIT, gameLocal.time - 3000, 2, (rvStat**)lastHits );
if( lastHits[ 1 ] && lastHits[ 1 ]->GetVictimClientNum() != playerClientNum && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", lastHits[ 1 ]->GetWeapon() ) ), "weapon_rocketlauncher" ) ) {
if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_RAIL_HIT ) {
statManager->GiveInGameAward( IGA_COMBO_KILL, playerClientNum );
}
}
}
rvStatManager::comboKillState[ playerClientNum ] = CKS_NONE;
// check for defense award
if( gameLocal.IsFlagGameType() && player->team != victim->team && player != victim ) {
// defense is given for two conditions
// assert( gameLocal.mpGame.GetFlagEntity( TEAM_STROGG ) && gameLocal.mpGame.GetFlagEntity( TEAM_MARINE ) );
assert( player->team >= 0 && player->team < TEAM_MAX );
assert( gameLocal.mpGame.GetGameState()->IsType( rvCTFGameState::GetClassType() ) );
if ( gameLocal.mpGame.GetFlagEntity( player->team ) ) {
// defending your flag
if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( player->team ) == FS_AT_BASE || ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( player->team ) == FS_DROPPED ) {
if( ( gameLocal.mpGame.GetFlagEntity( player->team )->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).LengthSqr() < 250000 ) {
// give award if you're close to flag and you kill an enemy
statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum );
} else if ( ( gameLocal.mpGame.GetFlagEntity( player->team )->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin() ).LengthSqr() < 250000 ) {
// give award if enemy is close to flag and you kill them
statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum );
}
}
}
// defending your teammate carrying the enemy flag
if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( gameLocal.mpGame.OpposingTeam( player->team ) ) == FS_TAKEN ) {
int clientNum = ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagCarrier( gameLocal.mpGame.OpposingTeam( player->team ) );
idPlayer* flagCarrier = NULL;
if( clientNum >= 0 && clientNum < MAX_CLIENTS ) {
flagCarrier = (idPlayer*)gameLocal.entities[ clientNum ];
}
if( flagCarrier ) {
if( (flagCarrier->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin()).LengthSqr() < 250000 ) {
// killed enemy while close to the flag carrier
statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum );
} else if( (flagCarrier->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin()).LengthSqr() < 250000 ) {
// killed an enemy who was close to teh flag carrier
statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum );
}
}
}
}
// check for holy shit award
if( gameLocal.IsFlagGameType() && player->team != victim->team && player != victim ) {
if( (player->team == TEAM_MARINE && victim->PowerUpActive( POWERUP_CTF_MARINEFLAG )) ||
(player->team == TEAM_STROGG && victim->PowerUpActive( POWERUP_CTF_STROGGFLAG )) ) {
if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( victim->team ) == FS_AT_BASE ) {
// fixme: something is broken with the mpgame state
if ( gameLocal.mpGame.GetFlagEntity( victim->team ) ) {
if( (gameLocal.mpGame.GetFlagEntity( victim->team )->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin()).LengthSqr() < 40000 ) {
statManager->GiveInGameAward( IGA_HOLY_SHIT, playerClientNum );
}
}
}
}
}
}
/*
================
rvStatDeath
A player died
================
*/
rvStatDeath::rvStatDeath( int t, int p, int mod ) : rvStat( t ) {
type = ST_DEATH;
playerClientNum = p;
RegisterInGame( statManager->GetPlayerStats() );
}
void rvStatDeath::RegisterInGame( rvPlayerStat* stats ) {
stats[ playerClientNum ].deaths++;
}
/*
================
rvStatDamageDealt
A player damaged another player
================
*/
rvStatDamageDealt::rvStatDamageDealt( int t, int p, int w, int d ) : rvStat( t ) {
playerClientNum = p;
weapon = w;
damage = d;
type = ST_DAMAGE_DEALT;
RegisterInGame( statManager->GetPlayerStats() );
}
void rvStatDamageDealt::RegisterInGame( rvPlayerStat* stats ) {
if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS ) {
stats[ playerClientNum ].damageGiven += damage;
}
}
/*
================
rvStatDamageTaken
A player took damage from another player
================
*/
rvStatDamageTaken::rvStatDamageTaken( int t, int p, int w, int d ) : rvStat( t ) {
playerClientNum = p;
weapon = w;
damage = d;
type = ST_DAMAGE_TAKEN;
RegisterInGame( statManager->GetPlayerStats() );
}
void rvStatDamageTaken::RegisterInGame( rvPlayerStat* stats ) {
if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS ) {
stats[ playerClientNum ].damageTaken += damage;
}
}
/*
================
rvStatFlagDrop
A player dropped the flag (CTF)
================
*/
rvStatFlagDrop::rvStatFlagDrop( int t, int p, int a, int tm ) : rvStatTeam( t, tm ) {
playerClientNum = p;
attacker = a;
type = ST_CTF_FLAG_DROP;
}
/*
================
rvStatFlagReturn
A player returned his teams flag
================
*/
rvStatFlagReturn::rvStatFlagReturn( int t, int p, int tm ) : rvStatTeam( t, tm ) {
playerClientNum = p;
type = ST_CTF_FLAG_RETURN;
}
/*
================
rvStatFlagCapture
A player captured a flag (CTF)
================
*/
rvStatFlagCapture::rvStatFlagCapture( int t, int p, int f, int tm ) : rvStatTeam( t, tm ) {
playerClientNum = p;
flagTeam = f;
type = ST_CTF_FLAG_CAPTURE;
RegisterInGame( statManager->GetPlayerStats() );
}
void rvStatFlagCapture::RegisterInGame( rvPlayerStat* stats ) {
statManager->GiveInGameAward( IGA_CAPTURE, playerClientNum );
// figure out if we need to give an assist
// see if someone carried the flag a bit, lost it, but it was capped
rvStatFlagDrop* flagDrop = (rvStatFlagDrop*)statManager->GetLastTeamStat( team, ST_CTF_FLAG_DROP, gameLocal.time - 10000 );
if( flagDrop && flagDrop->GetTeam() == team ) {
// only give the award if there was no flag return between the flag drop and the flag capture
rvStatFlagReturn* flagReturn = (rvStatFlagReturn*)statManager->GetLastTeamStat( flagTeam, ST_CTF_FLAG_RETURN, gameLocal.time - 10000 );
if( flagReturn == NULL ) {
statManager->GiveInGameAward( IGA_ASSIST, flagDrop->GetPlayerClientNum() );
}
}
// see if someone returned the flag, allowing a cap
rvStatFlagReturn* flagReturn = (rvStatFlagReturn*)statManager->GetLastTeamStat( team, ST_CTF_FLAG_RETURN, gameLocal.time - 10000 );
if( flagReturn ) {
statManager->GiveInGameAward( IGA_ASSIST, flagReturn->GetPlayerClientNum() );
}
}
+361
View File
@@ -0,0 +1,361 @@
//----------------------------------------------------------------
// StatEvent.h
//
// Copyright 2002-2005 Raven Software
//----------------------------------------------------------------
#ifndef __STATEVENT_H__
#define __STATEVENT_H__
/*
===============================================================================
Multiplayer statistics events
This file contains statistic event definitions. rvStatManager exposes
game-related functions (i.e. 'Kill', 'FlagCaptured') which create the
appropriate statistic event and add it to the statQueue in rvStatManager.
The statQueue contains a complete picture of an MP game. It can be parsed to
calculate accuracies, award end-game awards, etc.
Statistic events also have functionality to register in-game information for
on-the-fly statistics.
===============================================================================
*/
class rvPlayerStat;
/*
================
statType_t
Type identifiers for RTTI of stat events, rvStatTeam-derived events must go
after ST_STAT_TEAM
================
*/
enum statType_t {
// not an actual event, undefined marker
ST_NONE = 0,
// rvStat derived
ST_BEGIN_GAME,
ST_END_GAME,
ST_CLIENT_CONNECT,
ST_HIT,
ST_KILL,
ST_DEATH,
ST_DAMAGE_DEALT,
ST_DAMAGE_TAKEN,
// not an actual event, team marker
ST_STAT_TEAM,
// rvStatTeam derived
ST_CTF_FLAG_CAPTURE,
ST_CTF_FLAG_DROP,
ST_CTF_FLAG_RETURN,
ST_COUNT
};
/*
================
rvStat
An individual event we want to know about for stats.
================
*/
class rvStat {
friend class rvStatAllocator;
public:
statType_t GetType( void ) const { return type; };
int GetTimeStamp( void ) const { return timeStamp; };
byte GetPlayerClientNum( void ) const { return playerClientNum; };
protected:
// moved to protected to prevent allocating these on the normal heap.
// these should only be allocatd by rvStatAllocator.
rvStat( int t ) { timeStamp = t; playerClientNum = -1; type = ST_NONE; };
virtual ~rvStat( void ) {};
virtual void RegisterInGame( rvPlayerStat* stats ) {};
// the entity number of the player associated with this statistic
byte playerClientNum;
statType_t type;
int timeStamp;
private:
// because of the way memory is handled for these we want the compiler
// on our side to find abuses. note that we aren't defining these.
// this change is propagated to all derived classes.
rvStat();
rvStat( const rvStat &rhs );
const rvStat &operator=( const rvStat &rhs );
};
/*
================
rvStatTeam
A team-related rvStat
================
*/
class rvStatTeam : public rvStat {
friend class rvStatAllocator;
public:
virtual ~rvStatTeam( void ) {};
int GetTeam( void ) { return team; };
protected:
// see comment in rvStat
rvStatTeam( int t, int tm ) : rvStat( t ) { team = tm; };
byte team;
private:
// see comment in rvStat
rvStatTeam();
rvStatTeam( const rvStatTeam &rhs );
const rvStatTeam &operator=( const rvStat &rhs );
};
/*
===============================================================================
rvStat/rvStatTeam-derived classes
These are the individual events that get stored in the
statManager's stat queue
===============================================================================
*/
/*
================
rvStatBeginGame
A game has begun
================
*/
class rvStatBeginGame : public rvStat {
friend class rvStatAllocator;
protected:
rvStatBeginGame( int t ) : rvStat( t ) { type = ST_BEGIN_GAME; };
};
/*
================
rvStatEndGame
The current game has ended
================
*/
class rvStatEndGame : public rvStat {
friend class rvStatAllocator;
protected:
rvStatEndGame( int t ) : rvStat( t ) { type = ST_END_GAME; };
};
/*
================
rvStatClientConnect
A player has connected
================
*/
class rvStatClientConnect : public rvStat {
friend class rvStatAllocator;
protected:
rvStatClientConnect( int t, int p ) : rvStat( t ) { type = ST_CLIENT_CONNECT; playerClientNum = p; };
};
/*
================
rvStatHit
A player hit another player
================
*/
class rvStatHit : public rvStat {
friend class rvStatAllocator;
public:
int GetVictimClientNum() const { return victimClientNum; }
int GetWeapon() const { return weapon; }
protected:
rvStatHit( int t, int p, int v, int w, bool countForAccuracy );
virtual void RegisterInGame( rvPlayerStat* stats );
byte weapon;
byte victimClientNum;
bool trackAccuracy;
};
/*
================
rvStatKill
A player killed another player
================
*/
class rvStatKill : public rvStat {
friend class rvStatAllocator;
protected:
rvStatKill( int t, int p, int v, bool g, int mod );
virtual void RegisterInGame( rvPlayerStat* stats );
byte methodOfDeath;
byte victimClientNum;
bool gibbed;
};
/*
================
rvStatDeath
A player died
================
*/
class rvStatDeath : public rvStat {
friend class rvStatAllocator;
protected:
rvStatDeath( int t, int p, int mod );
virtual void RegisterInGame( rvPlayerStat* stats );
byte methodOfDeath;
};
/*
================
rvStatDamageDealt
A player damaged another player
================
*/
class rvStatDamageDealt : public rvStat {
friend class rvStatAllocator;
public:
int GetDamage() const { return damage; }
protected:
rvStatDamageDealt( int t, int p, int w, int d );
virtual void RegisterInGame( rvPlayerStat* stats );
byte weapon;
short damage;
};
/*
================
rvStatDamageTaken
A player took damage from another player
================
*/
class rvStatDamageTaken : public rvStat {
friend class rvStatAllocator;
public:
int GetDamage() const { return damage; }
protected:
rvStatDamageTaken( int t, int p, int w, int d );
virtual void RegisterInGame( rvPlayerStat* stats );
byte weapon;
short damage;
};
/*
================
rvStatFlagDrop
A player dropped the flag (CTF)
================
*/
class rvStatFlagDrop : public rvStatTeam {
friend class rvStatAllocator;
protected:
rvStatFlagDrop( int t, int p, int a, int tm );
byte attacker; // enemy who caused the flag drop
private:
// see comment in rvStat
rvStatFlagDrop();
rvStatFlagDrop( const rvStatFlagDrop &rhs );
const rvStatFlagDrop &operator=( const rvStatFlagDrop &rhs );
};
/*
================
rvStatFlagReturn
A player returned his teams flag
================
*/
class rvStatFlagReturn : public rvStatTeam {
friend class rvStatAllocator;
protected:
rvStatFlagReturn( int t, int p, int tm );
private:
// see comment in rvStat
rvStatFlagReturn();
rvStatFlagReturn( const rvStatFlagReturn &rhs );
const rvStatFlagReturn &operator=( const rvStatFlagReturn &rhs );
};
/*
================
rvStatFlagCapture
A player captured a flag (CTF)
================
*/
class rvStatFlagCapture : public rvStatTeam {
friend class rvStatAllocator;
protected:
rvStatFlagCapture( int t, int p, int f, int tm );
virtual void RegisterInGame( rvPlayerStat* stats );
byte flagTeam; // team of flag was captured
private:
// see comment in rvStat
rvStatFlagCapture();
rvStatFlagCapture( const rvStatFlagCapture &rhs );
const rvStatFlagCapture &operator=( const rvStatFlagCapture &rhs );
};
#endif
File diff suppressed because it is too large Load Diff
+349
View File
@@ -0,0 +1,349 @@
//----------------------------------------------------------------
// StatManager.h
//
// Copyright 2002-2004 Raven Software
//----------------------------------------------------------------
#ifndef __STATMANAGER_H__
#define __STATMANAGER_H__
#include "StatEvent.h"
#include "StatWindow.h"
#ifndef ID_TRAFFICSTATS
#define ID_TRAFFICSTATS 0
#endif
/*
===============================================================================
Multiplayer game statistics
===============================================================================
*/
/*
================
endGameAward_t
End-game award IDs
================
*/
enum endGameAward_t {
EGA_INVALID = 0,
EGA_LEMMING,
EGA_RAIL_MASTER,
EGA_ROCKET_SAUCE,
EGA_BRAWLER,
EGA_SNIPER,
EGA_CRITICAL_FAILURE,
EGA_TEAM_PLAYER,
EGA_ACCURACY,
EGA_FRAGS,
EGA_PERFECT,
EGA_NUM_AWARDS
};
/*
================
endGameAwardInfo_t
end-game award info
================
*/
struct endGameAwardInfo_t {
char* name;
};
/*
================
inGameAward_t
in-game award IDs
================
*/
enum inGameAward_t {
IGA_INVALID = 0,
IGA_CAPTURE,
IGA_HUMILIATION,
IGA_IMPRESSIVE,
IGA_EXCELLENT,
IGA_ASSIST,
IGA_DEFENSE,
IGA_COMBO_KILL,
IGA_RAMPAGE,
IGA_HOLY_SHIT,
IGA_NUM_AWARDS
};
enum comboKillState_t {
CKS_NONE,
CKS_ROCKET_FIRED,
CKS_ROCKET_HIT,
CKS_RAIL_FIRED,
CKS_RAIL_HIT,
};
/*
================
inGameAwardInfo_t
in-game award info
================
*/
struct inGameAwardInfo_t {
char* name;
};
/*
================
rvStatAllocator
A simple block allocator for different stat objects.
Note: this isn't a real heap; it has no delete or tracking of free blocks.
It matches the memory usage of the rvStatManager which dumps its entire
memory all at once at very specific times. If that ever changes, this will
need to change.
================
*/
const int BLOCK_SIZE = 1024;
const int MAX_BLOCKS = 128;
class rvStatAllocator {
protected:
byte blocks[ BLOCK_SIZE * MAX_BLOCKS ];
short currentBlock; // current block
size_t placeInBlock; // current position, offset from start of block
size_t totalBytesUsed; // total bytes handed out from the allocator
size_t totalAllocations; // total blocks handed out from the allocator
size_t totalBytesAllocated; // total bytes in all allocated blocks
size_t allocationsByType[ST_COUNT]; // one spare at [0]; allocations per type of event
void *GetBlock( size_t blockSize, int* blockNumOut = NULL );
public:
rvStatAllocator();
void Report();
void Reset();
// object allocators
rvStatBeginGame *AllocStatBeginGame ( int t, int* blockNumOut = NULL );
rvStatEndGame *AllocStatEndGame ( int t, int* blockNumOut = NULL );
rvStatClientConnect *AllocStatClientConnect ( int t, int client, int* blockNumOut = NULL );
rvStatHit *AllocStatHit ( int t, int p, int v, int w, bool countForAccuracy, int* blockNumOut = NULL );
rvStatKill *AllocStatKill ( int t, int p, int v, bool g, int mod, int* blockNumOut = NULL );
rvStatDeath *AllocStatDeath ( int t, int p, int mod, int* blockNumOut = NULL );
rvStatDamageDealt *AllocStatDamageDealt ( int t, int p, int w, int d, int* blockNumOut = NULL );
rvStatDamageTaken *AllocStatDamageTaken ( int t, int p, int w, int d, int* blockNumOut = NULL );
rvStatFlagDrop *AllocStatFlagDrop ( int t, int p, int a, int tm, int* blockNumOut = NULL );
rvStatFlagReturn *AllocStatFlagReturn ( int t, int p, int tm, int* blockNumOut = NULL );
rvStatFlagCapture *AllocStatFlagCapture ( int t, int p, int f, int tm, int* blockNumOut = NULL );
// bookkeeping readers
ID_INLINE size_t GetBytesLeftInBlock() const {
assert( placeInBlock <= BLOCK_SIZE );
return ( size_t )( BLOCK_SIZE - placeInBlock );
}
ID_INLINE size_t GetPlaceInBlock() const {
return placeInBlock;
}
ID_INLINE size_t GetTotalBytesUsed() const {
return totalBytesUsed;
}
ID_INLINE size_t GetTotalAllocations() const {
return totalAllocations;
}
ID_INLINE size_t GetAllocationsByType( statType_t type ) const {
return allocationsByType[ type ];
}
ID_INLINE size_t GetTotalBytesAllocated() const {
return totalBytesAllocated;
}
};
/*
================
rvPlayerStat
Stores one player's stat information.
================
*/
class rvPlayerStat {
public:
rvPlayerStat();
void Clear( void );
void CalculateStats( int p, const idList<rvStat*>& playerStats );
void PackStats( idBitMsg& msg );
void UnpackStats( const idBitMsg& msg );
int weaponShots[ MAX_WEAPONS ];
int weaponHits[ MAX_WEAPONS ];
int weaponKills[ MAX_WEAPONS ];
int kills;
int deaths;
int suicides;
float damageRatio;
// asalmon: added for Xenon
// ddynerman: also used on PC
int damageGiven;
int damageTaken;
idList<endGameAward_t> endGameAwards;
// asalmon: changed this to just an array of counts
int inGameAwards[IGA_NUM_AWARDS];
int lastUpdateTime;
};
/*
================
rvStatManager
The statistics management interface. This is the entrypoint into
statistics from the game.
The game calls into rvStatManager at appropriate times (i.e. 'FlagCaptured')
rvStatManager creates stat events (see StatEvent.h) with the appropriate
information and append them to the statQueue.
The statQueue is parsed at the end of a game to tabulate statistics, give
end-game awards.
================
*/
class rvStatManager {
public:
rvStatManager();
void Init( void );
void Shutdown( void );
// generic events
void BeginGame( void );
void EndGame( void );
void ClientConnect( int clientNum );
void ClientDisconnect( int clientNum );
void WeaponFired( const idPlayer* player, int weapon, int num );
void WeaponHit( const idActor* attacker, const idEntity* victim, int weapon, bool countForAccuracy = true );
void Damage( const idEntity* attacker, const idEntity* victim, int weapon, int damage );
// ctf-specific events
void FlagCaptured( const idPlayer* player, int flagTeam );
void FlagDropped( const idPlayer* player, const idEntity* attacker );
void FlagReturned( const idPlayer* player );
// shared events
void Kill( const idPlayer* victim, const idEntity* killer, int methodOfDeath );
void DebugPrint( void );
rvPlayerStat* GetPlayerStats( void ) { return playerStats; };
rvPlayerStat* GetPlayerStat( int clientNum );
void CalculateEndGameStats( void );
void GivePlayerCashForAward( idPlayer* player, inGameAward_t award );
void GiveInGameAward( inGameAward_t award, int clientNum );
// network transmission
void SendStat( int toClient, int statClient );
void ReceiveStat( const idBitMsg& msg );
void SendInGameAward( inGameAward_t award, int clientNum );
void ReceiveInGameAward( const idBitMsg& msg );
void CheckAwardQueue();
void ClearStats( void );
int DamageGiven( int playerNum, int lowerBound = idMath::INT_MIN, int upperBound = idMath::INT_MAX );
int DamageTaken( int playerNum, int lowerBound = idMath::INT_MIN, int upperBound = idMath::INT_MAX );
void UpdateInGameHud( idUserInterface* statHud, bool visible );
void UpdateEndGameHud( idUserInterface* statHud, int clientNum );
void SetupEndGameHud( idUserInterface* statHud );
rvStat* GetLastClientStat( int clientNum, statType_t type, int time );
void GetLastClientStats( int clientNum, statType_t type, int time, int num, rvStat** results );
rvStatTeam* GetLastTeamStat( int team, statType_t type, int time );
rvStat* GetStat( int i );
void GetAccuracyLeaders( int accuracyLeaders[ MAX_WEAPONS ] );
void SetupStatWindow( idUserInterface* statHud );
void SelectStatWindow( int selectionIndex, int selectionTeam );
int GetSelectedClientNum( int* selectionIndexOut = NULL, int* selectionTeamOut = NULL );
//asalmon: Sends all stats to all clients. For Xenon periodic update of stats.
void SendAllStats( int clientNum = -1, bool full = true );
void ReceiveAllStats( const idBitMsg& msg );
void SetDamageGiven(int client, int damage) { playerStats[client].damageGiven = damage; }
void SetDamageTaken(int client, int damage) { playerStats[client].damageTaken = damage; }
int FreeEvents( int blockNum );
static comboKillState_t comboKillState[ MAX_CLIENTS ];
static int lastRailShot[ MAX_CLIENTS ];
static int lastRailShotHits[ MAX_CLIENTS ];
private:
idList<rvPair<rvStat*, int> > statQueue;
idList<rvPair<int, bool> > awardQueue;
// idList<rvStatInGame> inGameStats;
// rvStatInGame previousInGameStats[ MAX_CLIENTS ];
// rvStatInGame localClientInGameStats;
// local hud support
int localInGameAwards[ IGA_NUM_AWARDS ];
int inGameAwardHudTime;
rvPlayerStat playerStats[ MAX_CLIENTS ];
bool endGameSetup;
rvStatWindow statWindow;
// shouchard: stat allocator to avoid lots of little news
rvStatAllocator statAllocator;
#ifdef _XENON
int lastFullUpdate;
#endif
#if ID_TRAFFICSTATS
int startSent;
int startPacketsSent;
int startReceived;
int startPacketsReceived;
int startTime;
#endif
};
ID_INLINE rvStat* rvStatManager::GetStat( int i ) {
if( i < 0 || i >= statQueue.Num() ) {
return NULL;
}
return statQueue[ i ].First();
}
extern rvStatManager* statManager;
extern inGameAwardInfo_t inGameAwardInfo[ IGA_NUM_AWARDS ];
extern endGameAwardInfo_t endGameAwardInfo[ EGA_NUM_AWARDS ];
#endif /* !__STATMANAGER_H__ */
+462
View File
@@ -0,0 +1,462 @@
//----------------------------------------------------------------
// StatWindow.cpp
//
// Copyright 2002-2005 Raven Software
//----------------------------------------------------------------
#include "precompiled.h"
#pragma hdrstop
#include "../../Game_local.h"
#include "StatWindow.h"
#include "StatManager.h"
/*
================
rvStatWindow::rvStatWindow()
================
*/
rvStatWindow::rvStatWindow() {
statHud = NULL;
}
/*
================
rvStatWindow::SetupStatWindow()
Sets up a selectable window of current stats
================
*/
void rvStatWindow::SetupStatWindow( idUserInterface* hud, bool useSpectator ) {
idPlayer* player = gameLocal.GetLocalPlayer();
assert( player );
if ( !player ) {
return;
}
// mekberg: added
idList<int> marineScores;
idList<int> stroggScores;
int selectionIndex = -1;
int selectionTeam = -1;
statHud = hud;
if( gameLocal.IsTeamGame() ) {
if( gameLocal.IsFlagGameType() ) {
statHud->SetStateInt( "ctf_awards", 1 );
} else {
statHud->SetStateInt( "ctf_awards", 0 );
}
stroggPlayers.Clear();
marinePlayers.Clear();
for( int i = 0; i < gameLocal.mpGame.GetNumRankedPlayers(); i++ ) {
idPlayer* player = gameLocal.mpGame.GetRankedPlayer( i );
if( player->team == TEAM_MARINE ) {
marinePlayers.Append( player );
marineScores.Append( gameLocal.mpGame.GetRankedPlayerScore( i ) );
} else if ( player->team == TEAM_STROGG ) {
stroggPlayers.Append( player );
stroggScores.Append( gameLocal.mpGame.GetRankedPlayerScore( i ) );
}
}
for( int i = 0; i < MAX_CLIENTS; i++ ) {
if( i < marinePlayers.Num() ) {
statHud->SetStateString( va( "team_1_names_item_%d", i ),
va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( marinePlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ),
( player->IsPlayerMuted( marinePlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ),
marinePlayers[ i ]->GetUserInfo()->GetString( "ui_name"),
marineScores[ i ] ) );
if( useSpectator ) {
if( marinePlayers[ i ]->entityNumber == player->spectator ) {
selectionTeam = TEAM_MARINE;
selectionIndex = i;
}
} else {
if( marinePlayers[ i ] == player ) {
selectionTeam = TEAM_MARINE;
selectionIndex = i;
}
}
} else {
statHud->SetStateString( va( "team_1_names_item_%d", i ), "" );
}
}
for( int i = 0; i < MAX_CLIENTS; i++ ) {
if( i < stroggPlayers.Num() ) {
statHud->SetStateString( va( "team_2_names_item_%d", i ),
va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( stroggPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ),
( player->IsPlayerMuted( stroggPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ),
stroggPlayers[ i ]->GetUserInfo()->GetString( "ui_name"),
stroggScores[ i ] ) );
if( useSpectator ) {
if( stroggPlayers[ i ]->entityNumber == player->spectator ) {
selectionTeam = TEAM_STROGG;
selectionIndex = i;
}
} else {
if( stroggPlayers[ i ] == player ) {
selectionTeam = TEAM_STROGG;
selectionIndex = i;
}
}
} else {
statHud->SetStateString( va( "team_2_names_item_%d", i ), "" );
}
}
statHud->SetStateInt ( "num_strogg_players", stroggPlayers.Num() );
statHud->SetStateInt ( "num_marine_players", marinePlayers.Num() );
} else {
statHud->SetStateInt( "ctf_awards", 0 );
players.Clear();
for( int i = 0; i < gameLocal.mpGame.GetNumRankedPlayers(); i++ ) {
players.Append( gameLocal.mpGame.GetRankedPlayer( i ) );
}
for( int i = 0; i < MAX_CLIENTS; i++ ) {
if( i < players.Num() ) {
statHud->SetStateString( va( "dm_names_item_%d", i ),
va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( players[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ),
( player->IsPlayerMuted( players[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ),
players[ i ]->GetUserInfo()->GetString( "ui_name"),
gameLocal.mpGame.GetScore( players[ i ] ) ) );
if( useSpectator ) {
if( players[ i ]->entityNumber == player->spectator ) {
selectionTeam = 0;
selectionIndex = i;
}
} else {
if( players[ i ] == player ) {
selectionTeam = 0;
selectionIndex = i;
}
}
} else {
statHud->SetStateString( va( "dm_names_item_%d", i ), "" );
}
}
statHud->SetStateInt ( "num_players", players.Num() );
}
// spectators
spectators.Clear();
for( int i = 0; i < gameLocal.mpGame.GetNumUnrankedPlayers(); i++ ) {
spectators.Append( gameLocal.mpGame.GetUnrankedPlayer( i ) );
}
for( int i = 0; i < MAX_CLIENTS; i++ ) {
if( i < spectators.Num() ) {
statHud->SetStateString( va( "spec_names_item_%d", i ),
va( "%s\t%s\t%s\t", ( player->IsFriend( spectators[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ),
( player->IsPlayerMuted( spectators[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ),
spectators[ i ]->GetUserInfo()->GetString( "ui_name") ) );
if( useSpectator ) {
if( spectators[ i ]->entityNumber == player->spectator ) {
selectionTeam = TEAM_MAX;
selectionIndex = i;
}
} else {
if( spectators[ i ] == player ) {
selectionTeam = TEAM_MAX;
selectionIndex = i;
}
}
} else {
statHud->SetStateString( va( "spec_names_item_%d", i ), "" );
}
}
statHud->SetStateInt( "gametype", gameLocal.gameType );
statHud->SetStateInt( "playerteam", gameLocal.GetLocalPlayer()->team );
statHud->StateChanged ( gameLocal.time );
statHud->Redraw( gameLocal.time );
// we shouldn't ever draw a hud unless we're in-game
assert( selectionIndex >= 0 && selectionTeam >= 0 );
statManager->SelectStatWindow( selectionIndex, selectionTeam );
}
/*
================
rvStatWindow::ClearWindow()
Clears the stat part of the stat window, but not the player lists boxes
================
*/
void rvStatWindow::ClearWindow( void ) {
for( int i = 0; i < MAX_WEAPONS; i++ ) {
statHud->SetStateString( va( "stat_%d_pct", i ), "" );
}
for( int i = 0; i < IGA_NUM_AWARDS; i++ ) {
statHud->SetStateString( inGameAwardInfo[ i ].name, "" );
}
// end-game awards
for( int i = 0; i < EGA_NUM_AWARDS; i++ ) {
statHud->SetStateInt( va( "eg_award%d", i ), 0 );
statHud->SetStateString( va( "eg_award%d_text", i ), "" );
}
// kills
statHud->SetStateString( "stat_frags", "" );
// deaths
statHud->SetStateString( "stat_deaths", "" );
// score
statHud->SetStateString( "stat_score", "" );
statHud->StateChanged ( gameLocal.time );
statHud->Redraw( gameLocal.time );
}
/*
================
rvStatWindow::SelectPlayer()
Selects the specified player
================
*/
void rvStatWindow::SelectPlayer( int clientNum ) {
if( statHud == NULL ) {
return;
}
if( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
ClearWindow();
return;
}
assert( gameLocal.GetLocalPlayer() );
rvPlayerStat* clientStat = statManager->GetPlayerStat( clientNum );
if( gameLocal.isClient && ( clientStat == NULL || ( gameLocal.time - clientStat->lastUpdateTime ) > 5000 ) ) {
// get new stats
idBitMsg outMsg;
byte msgBuf[ 128 ];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STAT );
outMsg.WriteByte( clientNum );
networkSystem->ClientSendReliableMessage( outMsg );
return;
}
// weapon accuracy
for( int i = 0; i < MAX_WEAPONS; i++ ) {
int weaponAccuracy = 0;
if( clientStat->weaponShots[ i ] != 0 ) {
weaponAccuracy = (int)(((float)clientStat->weaponHits[ i ] / (float)clientStat->weaponShots[ i ]) * 100.0f);
}
statHud->SetStateString( va( "stat_%d_pct", i ), va( "%d%%", weaponAccuracy ) );
}
int igAwardCount[ IGA_NUM_AWARDS ];
memset( igAwardCount, 0, sizeof( int ) * IGA_NUM_AWARDS );
for( int i = 0; i < IGA_NUM_AWARDS; i++ ) {
igAwardCount[i] = 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 < EGA_NUM_AWARDS; i++ ) {
if( i < clientStat->endGameAwards.Num() ) {
statHud->SetStateInt( va( "eg_award%d", i ), 1 );
// RAVEN BEGIN
// rhummer: Localized the award strings..
statHud->SetStateString( va( "eg_award%d_text", i ), common->GetLocalizedString( endGameAwardInfo[ clientStat->endGameAwards[ i ] ].name ) );
// RAVEN END
} else {
statHud->SetStateInt( va( "eg_award%d", i), 0 );
}
}
// kills
statHud->SetStateString( "stat_frags", va( "%d", clientStat->kills ) );
// deaths
statHud->SetStateString( "stat_deaths", va( "%d", clientStat->deaths ) );
// score
int score = gameLocal.IsTeamGame() ? (gameLocal.mpGame.GetTeamScore( clientNum ) + gameLocal.mpGame.GetScore( clientNum )): gameLocal.mpGame.GetScore( clientNum );
statHud->SetStateString( "stat_score", va( "%d", score ) );
if( gameLocal.GetLocalPlayer()->IsFriend( clientNum ) ) {
// remove friend label
statHud->SetStateString( "friend_button", common->GetLocalizedString( "#str_200249" ) );
} else {
// add friend label
statHud->SetStateString( "friend_button", common->GetLocalizedString( "#str_200248" ) );
}
if( gameLocal.GetLocalPlayer()->IsPlayerMuted( clientNum ) ) {
// unmute label
statHud->SetStateString( "mute_button", common->GetLocalizedString( "#str_200251" ) );
} else {
// mute label
statHud->SetStateString( "mute_button", common->GetLocalizedString( "#str_200250" ) );
}
statHud->StateChanged ( gameLocal.time );
statHud->Redraw( gameLocal.time );
}
/*
================
rvStatWindow::ClientNumFromSelection()
Parses a selection index and team into a clientNum
================
*/
int rvStatWindow::ClientNumFromSelection( int selectionIndex, int selectionTeam ) {
int clientNum = -1;
if( gameLocal.IsTeamGame() ) {
if( selectionTeam == TEAM_MARINE ) {
if( selectionIndex < 0 || selectionIndex >= marinePlayers.Num() ) {
gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex );
return -1;
}
clientNum = marinePlayers[ selectionIndex ]->entityNumber;
// explicitly set the gui selection if we called in here not from a GUI
statHud->SetStateInt( "team_1_names_sel_0", selectionIndex );
statHud->SetStateString( "dm_names_sel_0", "-1" );
statHud->SetStateString( "spec_names_sel_0", "-1" );
statHud->SetStateString( "team_2_names_sel_0", "-1" );
} else if( selectionTeam == TEAM_STROGG ) {
if( selectionIndex < 0 || selectionIndex >= stroggPlayers.Num() ) {
gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex );
return -1;
}
clientNum = stroggPlayers[ selectionIndex ]->entityNumber;
// explicitly set the gui selection if we called in here not from a GUI
statHud->SetStateInt( "team_2_names_sel_0", selectionIndex );
statHud->SetStateString( "dm_names_sel_0", "-1" );
statHud->SetStateString( "spec_names_sel_0", "-1" );
statHud->SetStateString( "team_1_names_sel_0", "-1" );
} else {
if( selectionIndex < 0 || selectionIndex >= spectators.Num() ) {
gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex );
return -1;
}
clientNum = spectators[ selectionIndex ]->entityNumber;
// explicitly set the gui selection if we called in here not from a GUI
statHud->SetStateInt( "spec_names_sel_0", selectionIndex );
statHud->SetStateString( "dm_names_sel_0", "-1" );
statHud->SetStateString( "team_1_names_sel_0", "-1" );
statHud->SetStateString( "team_2_names_sel_0", "-1" );
}
} else {
if( selectionTeam == TEAM_MAX ) {
if( selectionIndex < 0 || selectionIndex >= spectators.Num() ) {
gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex );
return -1;
}
clientNum = spectators[ selectionIndex ]->entityNumber;
// explicitly set the gui selection if we called in here not from a GUI
statHud->SetStateInt( "spec_names_sel_0", selectionIndex );
statHud->SetStateString( "dm_names_sel_0", "-1" );
statHud->SetStateString( "team_1_names_sel_0", "-1" );
statHud->SetStateString( "team_2_names_sel_0", "-1" );
} else {
if( selectionIndex < 0 || selectionIndex >= players.Num() ) {
gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex );
return -1;
}
clientNum = players[ selectionIndex ]->entityNumber;
// explicitly set the gui selection if we called in here not from a GUI
statHud->SetStateInt( "dm_names_sel_0", selectionIndex );
statHud->SetStateString( "spec_names_sel_0", "-1" );
statHud->SetStateString( "team_1_names_sel_0", "-1" );
statHud->SetStateString( "team_2_names_sel_0", "-1" );
}
}
return clientNum;
}
/*
================
rvStatWindow::GetSelectedClientNum()
Queries the gui dict to figure out the currently selected selectionIndex/selectionTeam.
Uses the selectionIndex/selectionTeam to return the selected client num.
================
*/
int rvStatWindow::GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ) {
if( statHud == NULL ) {
return -1;
}
int selectionIndex = -1;
int selectionTeam = -1;
// StatWindow update code should assure that only one selection of all these listDefs is is valid
// Find the valid one
if( statHud->State().GetInt( "spec_names_sel_0", "-1" ) >= 0 ) {
selectionIndex = statHud->State().GetInt( "spec_names_sel_0", "-1" );
selectionTeam = TEAM_MAX;
}
if ( statHud->State().GetInt( "dm_names_sel_0", "-1" ) >= 0 ) {
// if this assert fails, more than one selection is valid
assert( selectionIndex == -1 && selectionTeam == -1 );
selectionIndex = statHud->State().GetInt( "dm_names_sel_0", "-1" );
selectionTeam = 0;
}
if ( statHud->State().GetInt( "team_1_names_sel_0", "-1" ) >= 0 ) {
// if this assert fails, more than one selection is valid
assert( selectionIndex == -1 && selectionTeam == -1 );
selectionIndex = statHud->State().GetInt( "team_1_names_sel_0", "-1" );
selectionTeam = TEAM_MARINE;
}
if ( statHud->State().GetInt( "team_2_names_sel_0", "-1" ) >= 0 ) {
// if this assert fails, more than one selection is valid
assert( selectionIndex == -1 && selectionTeam == -1 );
selectionIndex = statHud->State().GetInt( "team_2_names_sel_0", "-1" );
selectionTeam = TEAM_STROGG;
}
// return the selection index & team
if( selectionIndexOut ) {
(*selectionIndexOut) = selectionIndex;
}
if( selectionTeamOut ) {
(*selectionTeamOut) = selectionTeam;
}
return ClientNumFromSelection( selectionIndex, selectionTeam );
}
+36
View File
@@ -0,0 +1,36 @@
//----------------------------------------------------------------
// StatWindow.h
//
// Copyright 2002-2005 Raven Software
//----------------------------------------------------------------
#ifndef __STATWINDOW_H__
#define __STATWINDOW_H__
/*
===============================================================================
Stat selection window
===============================================================================
*/
class rvStatWindow {
public:
rvStatWindow();
void SetupStatWindow( idUserInterface* statHud, bool useSpectator = false );
void SelectPlayer( int clientNum );
int ClientNumFromSelection( int selectionIndex, int selectionTeam );
void ClearWindow( void );
int GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut );
private:
idList<idPlayer*> stroggPlayers;
idList<idPlayer*> marinePlayers;
idList<idPlayer*> players;
idList<idPlayer*> spectators;
idUserInterface* statHud;
};
#endif