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.
 
 
 
 
 
 

539 lines
16 KiB

//=========== Copyright Valve Corporation, All rights reserved. ==============//
#include "cbase.h"
#include "fatdemo.h"
#include "baseplayer_shared.h"
#include "cs_gamerules.h"
#include "gametypes/igametypes.h"
#ifdef CLIENT_DLL
#include "c_team.h"
#include "c_playerresource.h"
#include "c_cs_player.h"
#include "c_cs_playerresource.h"
#else
#include "team.h"
#include "cs_player.h"
#include "cs_player_resource.h"
#endif
#include "weapon_csbase.h"
#include "cs_weapon_parse.h"
#include "proto_oob.h" // For MAKE_4BYTES
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#if !defined( CSTRIKE_REL_BUILD )
// Globals
CCSFatDemoRecorder g_fatDemoRecorder;
CCSFatDemoRecorder *g_pFatDemoRecorder = &g_fatDemoRecorder;
ConVar csgo_fatdemo_enable( "csgo_fatdemo_enable", "0", FCVAR_RELEASE );
ConVar csgo_fatdemo_output( "csgo_fatdemo_output", "test.fatdem", FCVAR_RELEASE );
// The file structure is thus:
// FatDemoHeader
// for each protobuf message:
// size of message
// protobuf message
struct FatDemoHeader
{
uint32 m_magic; // Must be characters "GOML",
uint32 m_version; // Which version of the header. Protobuf mechanisms are used for the actual payloads.
};
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void CaptureGameState( MLGameState* pOutState );
void CaptureMatchState( MLMatchState* pOutState );
void CaptureRoundState( MLRoundState* pOutState );
void CapturePlayerState( MLPlayerState* pOutState, CCSPlayer* pCsPlayer );
void CaptureWeaponState( MLWeaponState* pOutState, CWeaponCSBase* pCsWeapon, int index, CCSPlayer* pCsPlayer );
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
class CCSFatDemoEventVisitor : public IGameEventVisitor2
{
public:
CCSFatDemoEventVisitor( MLEvent* pEvent )
: m_pEvent( pEvent )
{}
// IGameEventVisitor2
virtual bool VisitString( const char* name, const char* value ) OVERRIDE
{
MLDict* pEntry = m_pEvent->add_data();
pEntry->set_key( name );
pEntry->set_val_string( value );
return true;
}
virtual bool VisitFloat( const char* name, float value ) OVERRIDE
{
MLDict* pEntry = m_pEvent->add_data();
pEntry->set_key( name );
pEntry->set_val_float( value );
return true;
}
virtual bool VisitInt( const char* name, int value ) OVERRIDE
{
MLDict* pEntry = m_pEvent->add_data();
pEntry->set_key( name );
pEntry->set_val_int( value );
return true;
}
virtual bool VisitBool( const char*name, bool value ) OVERRIDE
{
MLDict* pEntry = m_pEvent->add_data();
pEntry->set_key( name );
pEntry->set_val_int( value ? 1 : 0 );
return true;
}
private:
MLEvent* m_pEvent;
};
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
CCSFatDemoRecorder::CCSFatDemoRecorder()
: m_tickcount( -1 )
, m_bInLevel( false )
, m_pCurrentTick( NULL )
{
}
// ------------------------------------------------------------------------------------------------
CCSFatDemoRecorder::~CCSFatDemoRecorder()
{
}
// ------------------------------------------------------------------------------------------------
void CCSFatDemoRecorder::Reset()
{
// Sync up the state of our trackers with the current state of the game.
}
// ------------------------------------------------------------------------------------------------
void CCSFatDemoRecorder::FireGameEvent( IGameEvent *pEvent )
{
if ( !csgo_fatdemo_enable.GetBool() )
return;
if ( !m_pCurrentTick )
return;
MLEvent* pOutEvent = m_pCurrentTick->add_events();
pOutEvent->set_event_name( pEvent->GetName() );
CCSFatDemoEventVisitor visitor( pOutEvent );
pEvent->ForEventData( &visitor );
}
// ------------------------------------------------------------------------------------------------
void CCSFatDemoRecorder::PostInit()
{
ListenForAllGameEvents();
}
// ------------------------------------------------------------------------------------------------
void CCSFatDemoRecorder::LevelInitPreEntity()
{
BeginFile();
m_bInLevel = true;
m_tickcount = -1;
}
// ------------------------------------------------------------------------------------------------
void CCSFatDemoRecorder::LevelShutdownPostEntity()
{
#ifdef _LINUX
bool bWasInLevel = m_bInLevel;
#endif
m_bInLevel = false;
FinalizeFile();
// Clean up our temp memory.
m_tempPacketStorage.Purge();
if ( m_pCurrentTick )
{
delete m_pCurrentTick;
m_pCurrentTick = NULL;
}
// There's an ugly crash in the bowels of scaleform that makes it hard for us to tell whether
// CSGO was actually successful or not. However, at this point we have been successful, so we
// should go ahead and exit with a success code if we're in demo_quitafterplayback mode (which
// is the usual case for autonomous capture.
#ifdef _LINUX
static ConVarRef demo_quitafterplayback( "demo_quitafterplayback" );
if ( bWasInLevel && demo_quitafterplayback.GetBool() )
{
_exit( 0 );
}
#endif
}
// ------------------------------------------------------------------------------------------------
void CCSFatDemoRecorder::OnTickPre( int tickcount )
{
if ( !csgo_fatdemo_enable.GetBool() )
return;
// Guard against multiple updates in the client if we're running a demo that isn't a timedemo.
if ( m_tickcount == tickcount )
return;
if ( !m_bInLevel )
return;
if ( !m_outFile )
return;
Assert( CSGameRules() );
if ( m_pCurrentTick )
{
m_pCurrentTick->set_tick_count( tickcount );
CaptureGameState( m_pCurrentTick->mutable_state() );
OutputProtobuf( m_pCurrentTick );
// TODO: This should be serialized or written out to a queue or something.
delete m_pCurrentTick;
m_pCurrentTick = NULL;
}
// Set up the current tick for next tick. We do this here so that any events captured from now
// until then affect the next tick (since we're done with this tick).
m_pCurrentTick = new MLTick;
// We've updated for this tick now.
m_tickcount = tickcount;
}
// ------------------------------------------------------------------------------------------------
void CCSFatDemoRecorder::OutputProtobuf( ::google::protobuf::Message* pProto )
{
Assert( pProto );
int32 size = pProto->ByteSize();
int32 totalSize = size + sizeof( int32 );
m_tempPacketStorage.EnsureCapacity( totalSize );
*( ( int32* ) m_tempPacketStorage.Base() ) = size;
if ( !pProto->SerializeToArray( ( ( byte* ) m_tempPacketStorage.Base() ) + sizeof( int ), size ) )
{
Assert( !"Serialization failed for... reasons." );
return;
}
g_pFullFileSystem->Write( m_tempPacketStorage.Base(), totalSize, m_outFile );
}
// ------------------------------------------------------------------------------------------------
void CCSFatDemoRecorder::BeginFile()
{
char buffer[MAX_PATH];
V_strcpy_safe( buffer, csgo_fatdemo_output.GetString() );
V_DefaultExtension( buffer, ".fatdem", sizeof( buffer ) );
m_outFile = g_pFullFileSystem->OpenEx( buffer, "wb" );
if ( !m_outFile )
return;
FatDemoHeader header;
header.m_magic = MAKE_4BYTES( 'G', 'O', 'M', 'L' );
header.m_version = 1;
g_pFullFileSystem->Write( &header, sizeof( header ), m_outFile );
// Now the protobuf header,
MLDemoHeader protoHeader;
#ifdef CLIENT_DLL
protoHeader.set_map_name( engine->GetLevelNameShort() );
#else
protoHeader.set_map_name( gpGlobals->mapname.ToCStr() );
#endif
if ( gpGlobals->interval_per_tick != 0.0f )
protoHeader.set_tick_rate(1 / gpGlobals->interval_per_tick );
#ifdef CLIENT_DLL
protoHeader.set_version( engine->GetClientVersion() );
#else
protoHeader.set_version( engine->GetServerVersion() );
#endif
#ifndef NO_STEAM
EUniverse eUniverse = steamapicontext && steamapicontext->SteamUtils()
? steamapicontext->SteamUtils()->GetConnectedUniverse()
: k_EUniverseInvalid;
protoHeader.set_steam_universe( ( int ) eUniverse );
#else
// Pretty sure this doesn't actually work anymore.
protoHeader.set_steam_universe( -1 );
#endif
OutputProtobuf( &protoHeader );
}
// ------------------------------------------------------------------------------------------------
void CCSFatDemoRecorder::FinalizeFile()
{
if ( m_outFile )
g_pFullFileSystem->Close( m_outFile );
m_outFile = 0;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void CaptureGameState( MLGameState* pOutState )
{
CaptureMatchState( pOutState->mutable_match() );
CaptureRoundState( pOutState->mutable_round() );
for ( int i = 1; i < MAX_PLAYERS; ++i )
{
CCSPlayer* pPlayer = dynamic_cast< CCSPlayer* >( UTIL_PlayerByIndex( i ) );
if ( !pPlayer )
continue;
CapturePlayerState( pOutState->add_players(), pPlayer );
}
}
// ------------------------------------------------------------------------------------------------
void CaptureMatchState( MLMatchState* pOutState )
{
char const *szGameMode = g_pGameTypes->GetGameModeFromInt( g_pGameTypes->GetCurrentGameType(), g_pGameTypes->GetCurrentGameMode() );
if ( !szGameMode || !*szGameMode )
szGameMode = "custom";
pOutState->set_game_mode( szGameMode );
char const *szPhase = "warmup";
bool bActivePhase = false;
if ( !CSGameRules()->IsWarmupPeriod() )
{
bActivePhase = true;
switch ( CSGameRules()->GetGamePhase() )
{
case GAMEPHASE_HALFTIME:
szPhase = "intermission";
break;
case GAMEPHASE_MATCH_ENDED:
szPhase = "gameover";
break;
default:
szPhase = "live";
break;
}
}
pOutState->set_phase( szPhase );
if ( bActivePhase )
pOutState->set_round( CSGameRules()->GetTotalRoundsPlayed() );
int nTeams[2] = { TEAM_CT, TEAM_TERRORIST };
int nScores[ 2 ] = { 0, 0 };
for ( int j = 0; j < 2; ++j )
{
auto *pTeam = GetGlobalTeam( nTeams[ j ] );
if ( !pTeam )
continue;
#ifdef CLIENT_DLL
nScores[ j ] = pTeam->Get_Score();
#else
nScores[ j ] = pTeam->GetScore();
#endif
}
pOutState->set_score_ct( nScores[ 0 ] );
pOutState->set_score_t( nScores[ 1 ] );
}
// ------------------------------------------------------------------------------------------------
void CaptureRoundState( MLRoundState* pOutState )
{
char const *szPhase = "freezetime";
if ( !CSGameRules()->IsFreezePeriod() )
{
if ( CSGameRules()->IsRoundOver() )
szPhase = "over";
else
szPhase = "live";
}
pOutState->set_phase( szPhase );
switch ( CSGameRules()->m_iRoundWinStatus )
{
case WINNER_CT:
pOutState->set_win_team( ET_CT );
break;
case WINNER_TER:
pOutState->set_win_team( ET_Terrorist );
break;
}
if ( CSGameRules()->IsBombDefuseMap() )
{
char const *szBombState = "";
if ( CSGameRules()->m_bBombPlanted && !CSGameRules()->IsRoundOver() )
szBombState = "planted";
if ( CSGameRules()->IsRoundOver() )
{
// Check if the bomb exploded or got defused?
switch ( CSGameRules()->m_eRoundWinReason )
{
case Target_Bombed:
szBombState = "exploded";
break;
case Bomb_Defused:
szBombState = "defused";
break;
}
}
if ( *szBombState )
pOutState->set_bomb_state( szBombState );
}
}
// ------------------------------------------------------------------------------------------------
static void DemoSetVector( CMsgVector* pOutVec, const Vector& inVec )
{
Assert( pOutVec );
pOutVec->set_x( inVec.x );
pOutVec->set_y( inVec.y );
pOutVec->set_z( inVec.z );
}
// ------------------------------------------------------------------------------------------------
static void DemoSetQAngle( CMsgQAngle* pOutAng, const QAngle& inAng )
{
Assert( pOutAng );
pOutAng->set_x( inAng.x );
pOutAng->set_y( inAng.y );
pOutAng->set_z( inAng.z );
}
// ------------------------------------------------------------------------------------------------
static void DemoSetQAngleAndForward( CMsgQAngle* pOutAng, CMsgVector* pOutVec, const QAngle& inAng )
{
DemoSetQAngle( pOutAng, inAng );
Vector fwd;
AngleVectors( inAng, &fwd );
DemoSetVector( pOutVec, fwd );
}
// ------------------------------------------------------------------------------------------------
void CapturePlayerState( MLPlayerState* pOutState, CCSPlayer* pCsPlayer )
{
CSteamID steamID;
if ( pCsPlayer->GetSteamID( &steamID ) )
pOutState->set_account_id( steamID.GetAccountID() );
pOutState->set_entindex( pCsPlayer->entindex() );
pOutState->set_name( pCsPlayer->GetPlayerName() );
// pOutState->set_clan( );
pOutState->set_team( ( ETeam )( pCsPlayer->GetTeamNumber() ) );
pOutState->set_user_id( pCsPlayer->GetUserID() );
DemoSetVector( pOutState->mutable_abspos(), pCsPlayer->GetAbsOrigin() );
DemoSetQAngleAndForward( pOutState->mutable_eyeangle(), pOutState->mutable_eyeangle_fwd(), pCsPlayer->EyeAngles() );
pOutState->set_health( pCsPlayer->GetHealth() );
pOutState->set_armor( pCsPlayer->ArmorValue() );
#ifdef CLIENT_DLL
pOutState->set_flashed( clamp( pCsPlayer->m_flFlashOverlayAlpha, 0.0f, 1.0f ) );
pOutState->set_smoked( clamp( pCsPlayer->GetLastSmokeOverlayAlpha(), 0.0f, 1.0f ) );
pOutState->set_money( pCsPlayer->GetAccount() );
pOutState->set_helmet( pCsPlayer->HasHelmet() );
#else
// TODO pOutState->set_flashed( clamp( pCsPlayer->m_flFlashOverlayAlpha, 0.0f, 1.0f ) );
// TODO pOutState->set_smoked( clamp( pCsPlayer->GetLastSmokeOverlayAlpha(), 0.0f, 1.0f ) );
pOutState->set_money( pCsPlayer->m_iAccount );
pOutState->set_helmet( pCsPlayer->m_bHasHelmet );
#endif
pOutState->set_round_kills( pCsPlayer->m_iNumRoundKills );
pOutState->set_round_killhs( pCsPlayer->m_iNumRoundKillsHeadshots );
pOutState->set_defuse_kit( pCsPlayer->HasDefuser() );
float flOnFireAmount = 0.0f;
if ( ( pCsPlayer->m_fMolotovDamageTime > 0.0f ) && ( gpGlobals->curtime - pCsPlayer->m_fMolotovDamageTime < 2 ) ) // took burn damage in last two seconds
{
flOnFireAmount = ( gpGlobals->curtime - pCsPlayer->m_fMolotovDamageTime <= 1.0f )
? 1.0f
: ( 2.0f - gpGlobals->curtime + pCsPlayer->m_fMolotovDamageTime );
}
pOutState->set_burning( clamp( flOnFireAmount, 0.0f, 1.0f ) );
int numWeapons = 0;
for ( int i = 0; i < pCsPlayer->WeaponCount(); ++i )
{
CWeaponCSBase* pCsWeapon = dynamic_cast< CWeaponCSBase * >( pCsPlayer->GetWeapon( i ) );
if ( !pCsWeapon )
continue;
CaptureWeaponState( pOutState->add_weapons(), pCsWeapon, numWeapons, pCsPlayer );
++numWeapons;
}
}
// ------------------------------------------------------------------------------------------------
void CaptureWeaponState( MLWeaponState* pOutState, CWeaponCSBase* pCsWeapon, int index, CCSPlayer* pCsPlayer )
{
pOutState->set_index( index );
pOutState->set_name( pCsWeapon->GetName() );
pOutState->set_type( ( EWeaponType ) pCsWeapon->GetWeaponType() );
pOutState->set_recoil_index( pCsWeapon->m_flRecoilIndex );
if ( pCsWeapon->m_iClip1 >= 0 )
pOutState->set_ammo_clip( pCsWeapon->m_iClip1 );
int iMaxClip1 = pCsWeapon->GetMaxClip1();
if ( iMaxClip1 > 0 )
pOutState->set_ammo_clip_max( iMaxClip1 );
if ( pCsWeapon->GetPrimaryAmmoType() >= 0 )
pOutState->set_ammo_reserve( pCsWeapon->GetReserveAmmoCount( AMMO_POSITION_PRIMARY ) );
char const *szState = "holstered";
if ( pCsPlayer->GetActiveCSWeapon() == pCsWeapon )
{
szState = "active";
if ( pCsWeapon->m_bInReload )
szState = "reloading";
}
pOutState->set_state( szState );
}
#endif