initial
This commit is contained in:
539
game/shared/cstrike15/fatdemo.cpp
Normal file
539
game/shared/cstrike15/fatdemo.cpp
Normal file
@@ -0,0 +1,539 @@
|
||||
//=========== 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
|
||||
Reference in New Issue
Block a user