//========= 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