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

509 lines
16 KiB

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: The CS game stats header
//
// $NoKeywords: $
//=============================================================================//
#ifndef CS_GAMESTATS_H
#define CS_GAMESTATS_H
#ifdef _WIN32
#pragma once
#endif
#include "cs_blackmarket.h"
#include "GameStats.h"
#include "cs_gamestats_shared.h"
#include "GameEventListener.h"
#include "weapon_csbase.h"
#include "steamworks_gamestats_server.h"
// forward declares
class CBreakableProp;
const float cDisseminationTimeHigh = 0.25f; // Time interval for high priority stats sent to the player
const float cDisseminationTimeLow = 2.5f; // Time interval for medium priority stats sent to the player
#define BULLET_SUB_GROUP_MASK 0xF0000000
#define BULLET_ID_GROUP_MASK 0x000FFFFF
#define BULLET_RECOIL_MASK 0x0FF00000
#define RECOIL_BIT_SHIFT(val) (val<<20)
#define SUB_BULLET_BIT_SHIFT(val) (val << 28 )
//Helper enum table and conversion function to simplify bomb data recording. Update whenever new bomb-related events become relevant
enum CSBombEventName
{
BOMB_EVENT_NAME_NONE = 0, // 0 is an unknown event
BOMB_EVENT_NAME_FIRST,
BOMB_EVENT_NAME_PLANTED = BOMB_EVENT_NAME_FIRST,
BOMB_EVENT_NAME_DEFUSED,
BOMB_EVENT_NAME_MAX = BOMB_EVENT_NAME_DEFUSED, // number of BOMB_EVENT_NAMES
};
CSBombEventName BombEventNameFromString( const char* pEventName );
int GetCSLevelIndex( const char *pLevelName );
#if !defined( NO_STEAM )
uint32 GetPlayerID( CCSPlayer *pPlayer );
#endif
typedef struct
{
char szGameName[8];
byte iVersion;
char szMapName[32];
char ipAddr[4];
short port;
int serverid;
} gamestats_header_t;
typedef struct
{
gamestats_header_t header;
short iMinutesPlayed;
short iTerroristVictories[CS_NUM_LEVELS];
short iCounterTVictories[CS_NUM_LEVELS];
short iBlackMarketPurchases[WEAPON_MAX];
short iAutoBuyPurchases;
short iReBuyPurchases;
short iAutoBuyM4A1Purchases;
short iAutoBuyAK47Purchases;
short iAutoBuyFamasPurchases;
short iAutoBuyGalilPurchases;
short iAutoBuyGalilARPurchases;
short iAutoBuyVestHelmPurchases;
short iAutoBuyVestPurchases;
} cs_gamestats_t;
struct WeaponStats
{
int shots;
int hits;
int kills;
int damage;
};
static byte PackVelocityComponent( float f )
{
// Gets velocity components and sticks fits them into a byte by scaling them
// and clamping them
f = f / 2.0f;
if ( f > 127 ) f = 127;
if ( f < -128 ) f = -128;
return (byte) f;
}
static byte PackMovementComponent( bool bDucking, bool bInAir)
{
return (((byte)bDucking) << 1) | (byte)bInAir;
}
static uint32 PackMovementStatInternal( Vector vVelocity, bool bDucking, bool bInAir )
{
return ( (uint32)PackVelocityComponent( vVelocity.x ) << 24 ) |
( (uint32)PackVelocityComponent( vVelocity.y ) << 16 ) |
( (uint32)PackVelocityComponent( vVelocity.z ) << 8 ) |
( (uint32)PackMovementComponent( bDucking, bInAir ) );
}
static uint32 PackPlayerMovementStat( CCSPlayer *pPlayer )
{
Vector vAttackerVelocity;
pPlayer->GetVelocity( &vAttackerVelocity, NULL );
bool bInAir = FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ? false : true;
bool bDucking = FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ? true : false;
return PackMovementStatInternal( vAttackerVelocity, bDucking, bInAir );
}
// Pack and convert Target Aim Angle component into 32bits
// OGS
static uint8 PackAimAngleComponent( float f )
{
// Pack component of QAngle into Byte. Angle is -180 -> 180, so there's no space for the full range.
// Make sure we're not getting weird values, rather have dumb data than broken game
if ( f > 180 ) f = 180;
if ( f < -180 ) f = -180;
// The greatest granularity comes from packing angle into ( 2 * Pi )/255 units.
// Effectively that's just a shift and stretch of the range: ( ( f + 180 )/ 360 ) * 255;
return (uint8) ( ( ( f + 180 ) / 360 ) * 255 );
}
static uint32 PackAimAngleStat( CCSPlayer *pPlayer )
{
QAngle vPlayerAimAngle = pPlayer->GetFinalAimAngle();
return ( (uint32)PackAimAngleComponent( (float)vPlayerAimAngle.z ) << 16 |
(uint32)PackAimAngleComponent( (float)vPlayerAimAngle.y ) << 8 |
(uint32)PackAimAngleComponent( (float)vPlayerAimAngle.x )
);
}
//=============================================================================
//
// OGS Gamestats
//
#if !defined( _GAMECONSOLE ) && !defined( NO_STEAM )
struct SWeaponShotData : public BaseStatData
{
SWeaponShotData( CCSPlayer *pPlayer, CWeaponCSBase* pWeapon, uint8 subBullet, uint8 round, uint8 iRecoilIndex )
{
Clear();
if ( pWeapon )
{
m_ui8WeaponID = (uint8)pWeapon->GetEconItemView()->GetItemIndex();
}
if ( pPlayer )
{
m_iUserID = GetPlayerID( pPlayer );
m_uiBulletID = pPlayer->GetBulletGroup();
m_vAttackerPos = pPlayer->GetAbsOrigin();
m_uAttackerMovement = PackPlayerMovementStat( pPlayer );
}
m_uiSubBulletID = subBullet;
m_RoundID = round;
m_uiRecoilIndex = iRecoilIndex;
}
void Clear()
{
m_iUserID = 0;
m_WeaponID = WEAPON_NONE;
m_ui8WeaponID = 0;
m_uiBulletID = 0;
m_uiSubBulletID = 0;
m_vAttackerPos.Init();
m_uAttackerMovement = 0;
m_RoundID = 0;
m_uiRecoilIndex = 0;
}
int m_iUserID;
CSWeaponID m_WeaponID;
uint8 m_ui8WeaponID;
uint32 m_uiBulletID;
uint8 m_uiSubBulletID;
uint8 m_uiRecoilIndex;
Vector m_vAttackerPos;
uint32 m_uAttackerMovement;
uint8 m_RoundID;
};
struct SWeaponHitData : public BaseStatData
{
SWeaponHitData( CCSPlayer *pCSTarget, const CTakeDamageInfo &info, uint8 subBullet, uint8 round, uint8 iRecoilIndex );
SWeaponHitData()
{
Clear();
}
// When any grenade explodes-- this is separate from any damage it may deal, which are also recorded as hits.
// Can fail! check the return!
bool InitAsGrenadeDetonation( class CBaseCSGrenadeProjectile *pGrenade, uint32 unBulletGroup );
// When a bomb is planted or defused, we want to collect data from each alive player, reporting their locations
bool InitAsBombEvent( CCSPlayer *pCSPlayer, class CPlantedC4 *pPlantedC4, uint32 unBulletGroup, uint8 nBombsite, CSBombEventName nBombEventName );
void Clear()
{
m_uiBulletID = 0;
m_uiSubBulletID = 0;
m_vAttackerPos.Init();
m_vTargetPos.Init();
m_uiDamage = 0;
m_HitRegion = 0;
m_RoundID = 0;
m_ui8WeaponID = 0;
m_ui64TargertID = 0;
m_ui64AttackerID = 0;
m_ui8Health = 0;
m_uAttackerMovement = 0;
m_uiRecoilIndex = 0;
}
void CompactBulletID()
{
m_uiBulletID = (m_uiBulletID & BULLET_ID_GROUP_MASK) | (SUB_BULLET_BIT_SHIFT(m_uiSubBulletID) & BULLET_SUB_GROUP_MASK) | (RECOIL_BIT_SHIFT(MIN(m_uiRecoilIndex, 255)) & BULLET_RECOIL_MASK);
}
// Data that gets sent to OGS
uint32 m_uiBulletID;
uint8 m_uiSubBulletID;
uint8 m_uiRecoilIndex;
Vector m_vAttackerPos;
Vector m_vTargetPos;
uint16 m_uiDamage;
uint8 m_HitRegion;
uint8 m_RoundID;
uint8 m_ui8Health;
uint8 m_ui8WeaponID;
uint32 m_ui64AttackerID;
uint32 m_ui64TargertID;
uint32 m_uAttackerMovement;
BEGIN_STAT_TABLE( "CSGOWeaponHitData" )
REGISTER_STAT_NAMED( m_ui8WeaponID, "WeaponID" )
REGISTER_STAT_NAMED( m_uiBulletID, "BulletID" )
REGISTER_STAT_NAMED( m_ui64AttackerID, "AttackerID" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.x, "AttackerX" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.y, "AttackerY" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.z, "AttackerZ" )
REGISTER_STAT_NAMED( (int)m_uAttackerMovement, "AttackerMovement" )
REGISTER_STAT_NAMED( m_ui64TargertID, "TargetID" )
REGISTER_STAT_NAMED( (int)m_vTargetPos.x, "TargetX" )
REGISTER_STAT_NAMED( (int)m_vTargetPos.y, "TargetY" )
REGISTER_STAT_NAMED( (int)m_vTargetPos.z, "TargetZ" )
REGISTER_STAT_NAMED( m_ui8Health, "Health" )
REGISTER_STAT_NAMED( m_uiDamage, "Damage" )
REGISTER_STAT_NAMED( m_HitRegion, "HitRegion" )
REGISTER_STAT_NAMED( m_RoundID, "Round" )
END_STAT_TABLE()
};
struct SWeaponMissData : public BaseStatData
{
SWeaponMissData( SWeaponShotData *data )
{
Clear();
if ( data )
{
m_ui64AttackerID = data->m_iUserID;
m_ui8WeaponID = data->m_ui8WeaponID;//data->m_WeaponID;
m_uiBulletID = data->m_uiBulletID;
m_uiRecoilIndex = data->m_uiRecoilIndex;
m_uiSubBulletID = data->m_uiSubBulletID;
m_vAttackerPos = data->m_vAttackerPos;
m_uAttackerMovement = data->m_uAttackerMovement;
m_RoundID = data->m_RoundID;
TimeSubmitted = data->TimeSubmitted;
}
}
void Clear()
{
m_ui8WeaponID = 0;
m_uiBulletID = 0;
m_uiSubBulletID = 0;
m_uiRecoilIndex = 0;
m_ui64AttackerID = 0;
m_vAttackerPos.Init();
m_uAttackerMovement = 0;
m_RoundID = 0;
}
void CompactBulletID()
{
m_uiBulletID = (m_uiBulletID & BULLET_ID_GROUP_MASK) | (SUB_BULLET_BIT_SHIFT(m_uiSubBulletID) & BULLET_SUB_GROUP_MASK) | (RECOIL_BIT_SHIFT(MIN(m_uiRecoilIndex, 255)) & BULLET_RECOIL_MASK);
}
uint8 m_ui8WeaponID;
uint32 m_uiBulletID;
uint8 m_uiSubBulletID;
uint8 m_uiRecoilIndex;
uint32 m_ui64AttackerID;
Vector m_vAttackerPos;
uint32 m_uAttackerMovement;
uint8 m_RoundID;
BEGIN_STAT_TABLE( "CSGOWeaponMissData" )
REGISTER_STAT_NAMED( m_ui8WeaponID, "WeaponID" )
REGISTER_STAT_NAMED( m_uiBulletID, "BulletID" )
REGISTER_STAT_NAMED( m_ui64AttackerID, "AttackerID" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.x, "AttackerX" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.y, "AttackerY" )
REGISTER_STAT_NAMED( (int)m_vAttackerPos.z, "AttackerZ" )
REGISTER_STAT_NAMED( (int)m_uAttackerMovement, "AttackerMovement" )
REGISTER_STAT_NAMED( m_RoundID, "Round" )
END_STAT_TABLE()
};
struct SMarketPurchases : public BaseStatData
{
SMarketPurchases( uint64 ulPlayerID, int iPrice, const char *pName, int round ) : ItemCost(iPrice)
{
m_nPlayerID = ulPlayerID;
if ( pName )
{
//Can we find a valid Item Definition?
if ( GetItemSchema()->GetItemDefinitionByName( pName ) )
{
ItemID = (uint)GetItemSchema()->GetItemDefinitionByName( pName )->GetDefinitionIndex();
}
else
{
//We don't know about you, you're probably equipment (armor, defuse kit)
//Use CSWeaponID instead (works for all equipment)
ItemID = WeaponIdFromString( pName ); //Returns WEAPON_NONE on failure to find string pName
}
}
else
{
ItemID = WEAPON_NONE;
}
// Can't buy 'none'. Investigate why we can't get a weapon ID from the given string.
Assert( ItemID != WEAPON_NONE );
m_iPurchaseCnt = 1;
m_niRound = round;
}
uint32 m_nPlayerID;
int ItemCost;
uint ItemID;
char m_iPurchaseCnt;
int m_niRound;
BEGIN_STAT_TABLE( "CSGOMarketPurchase" )
REGISTER_STAT_NAMED( m_nPlayerID, "AccountID" )
REGISTER_STAT( ItemCost )
REGISTER_STAT_NAMED( m_iPurchaseCnt, "PurchaseCount" )
REGISTER_STAT_NAMED( ItemID, "WeaponID" )
REGISTER_STAT_NAMED( m_niRound, "Round" )
END_STAT_TABLE()
};
typedef CUtlVector< SWeaponHitData* > CSGOWeaponHitData;
typedef CUtlVector< SWeaponMissData* > CSGOWeaponMissData;
typedef CUtlVector< SWeaponShotData* > CSGOWeaponShotsData;
typedef CUtlVector< SMarketPurchases* > CSGOMarketPurchaseData;
#endif // OGS Data
//=============================================================================
//
// CS Game Stats Class
//
class CCSGameStats : public CBaseGameStats, public CGameEventListener, public CAutoGameSystemPerFrame
#if !defined( _GAMECONSOLE )
, public IGameStatTracker
#endif
{
public:
// Constructor/Destructor.
CCSGameStats( void );
~CCSGameStats( void );
virtual void Clear( void );
virtual bool Init();
virtual void PreClientUpdate();
// Overridden events
virtual void Event_LevelInit( void );
virtual void Event_LevelShutdown( float flElapsed );
virtual void Event_ShotFired( CBasePlayer *pPlayer, CBaseCombatWeapon* pWeapon );
virtual void Event_ShotHit( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerKilled_PreWeaponDrop( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerConnected( CBasePlayer *pPlayer );
virtual void Event_PlayerDisconnected( CBasePlayer *pPlayer );
virtual void Event_WindowShattered( CBasePlayer *pPlayer );
virtual void Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info );
// CSS specific events
void Event_BombPlanted( CCSPlayer *pPlayer );
void Event_BombDefused( CCSPlayer *pPlayer );
void Event_BombExploded( CCSPlayer *pPlayer );
void Event_MoneyEarned( CCSPlayer *pPlayer, int moneyEarned );
void Event_MoneySpent( CCSPlayer* pPlayer, int moneySpent, const char *pItemName );
void Event_HostageRescued( CCSPlayer *pPlayer );
void Event_PlayerSprayedDecal( CCSPlayer*pPlayer );
void Event_AllHostagesRescued();
void Event_BreakProp( CCSPlayer *pPlayer, CBreakableProp *pProp );
void Event_PlayerDonatedWeapon (CCSPlayer* pPlayer);
void Event_PlayerDominatedOther( CCSPlayer* pAttacker, CCSPlayer* pVictim);
void Event_PlayerRevenge( CCSPlayer* pAttacker );
void Event_PlayerAvengedTeammate( CCSPlayer* pAttacker, CCSPlayer* pAvengedPlayer );
void Event_MVPEarned( CCSPlayer* pPlayer );
void Event_KnifeUse( CCSPlayer* pPlayer, bool bStab, int iDamage );
void RecordWeaponHit( SWeaponHitData* pHitData );
// Steamworks Gamestats
#if !defined( _GAMECONSOLE )
void UploadRoundStats( void );
virtual void SubmitGameStats( KeyValues *pKV );
virtual StatContainerList_t* GetStatContainerList( void );
bool AnyOGSDataToSubmit( void );
#endif
virtual void FireGameEvent( IGameEvent *event );
void UpdatePlayerRoundStats(int winner);
void DumpMatchWeaponMetrics();
const PlayerStats_t& FindPlayerStats( CBasePlayer *pPlayer ) const;
void ResetPlayerStats( CBasePlayer *pPlayer );
void ResetKillHistory( CBasePlayer *pPlayer );
void ResetRoundStats();
void ResetPlayerClassMatchStats();
void ClearOGSRoundStats();
const StatsCollection_t& GetTeamStats( int iTeamIndex ) const;
void ResetAllTeamStats();
void ResetAllStats();
void ResetWeaponStats();
void IncrementTeamStat( int iTeamIndex, int iStatIndex, int iAmount );
void CalcDominationAndRevenge( CCSPlayer *pAttacker, CCSPlayer *pVictim, int *piDeathFlags );
void CalculateOverkill(CCSPlayer* pAttacker, CCSPlayer* pVictim);
void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info );
void IncrementStat( CCSPlayer* pPlayer, CSStatType_t statId, int iValue, bool bPlayerOnly = false, bool bIncludeBotController = false );
void SendStatsToPlayer( CCSPlayer * pPlayer, int iMinStatPriority );
void CreateNewGameStatsSession( void );
protected:
void SetStat( CCSPlayer *pPlayer, CSStatType_t statId, int iValue );
void TrackKillStats( CCSPlayer *pAttacker, CCSPlayer *pVictim );
private:
PlayerStats_t m_aPlayerStats[MAX_PLAYERS+1]; // List of stats for each player for current life - reset after each death
StatsCollection_t m_aTeamStats[TEAM_MAXCOUNT - FIRST_GAME_TEAM];
float m_fDisseminationTimerLow; // how long since last medium priority stat update
float m_fDisseminationTimerHigh; // how long since last high priority stat update
int m_numberOfRoundsForDirectAverages;
int m_numberOfTerroristEntriesForDirectAverages;
int m_numberOfCounterTerroristEntriesForDirectAverages;
CUtlDict< CSStatType_t, short > m_PropStatTable;
WeaponStats m_weaponStats[WEAPON_MAX][WeaponMode_MAX];
// Steamworks Gamestats
#if !defined( _GAMECONSOLE ) && !defined( NO_STEAM )
CSGOWeaponHitData m_WeaponHitData;
CSGOWeaponMissData m_WeaponMissData;
CSGOWeaponShotsData m_WeaponShotData;
CSGOMarketPurchaseData m_MarketPurchases;
// A static list of all the stat containers, one for each data structure being tracked
static StatContainerList_t * s_StatLists;
#endif
};
extern CCSGameStats CCS_GameStats;
#endif // CS_GAMESTATS_H