//========= Copyright Valve Corporation, All rights reserved. ============// // //=======================================================================================// #include "cbase.h" #if defined( REPLAY_ENABLED ) #include "tf_replay.h" #include "tf/tf_shareddefs.h" #include "tf/c_tf_player.h" #include "tf/c_tf_playerresource.h" #include "tf/c_tf_gamestats.h" #include "tf/tf_gamestats_shared.h" #include "tf/tf_hud_statpanel.h" #include "tf/c_obj_sentrygun.h" #include "clientmode_shared.h" #include "replay/ireplaymoviemanager.h" #include "replay/ireplayfactory.h" #include "replay/ireplayscreenshotmanager.h" #include "replay/screenshot.h" #include //---------------------------------------------------------------------------------------- extern IReplayScreenshotManager *g_pReplayScreenshotManager; //---------------------------------------------------------------------------------------- CTFReplay::CTFReplay() : m_flNextMedicUpdateTime( 0.0f ) { } CTFReplay::~CTFReplay() { } void CTFReplay::OnBeginRecording() { BaseClass::OnBeginRecording(); // Setup the newly created replay C_TFPlayer* pPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pPlayer ) { if ( pPlayer->GetPlayerClass() ) { SetPlayerClass( pPlayer->GetPlayerClass()->GetClassIndex() ); } SetPlayerTeam( pPlayer->GetTeamNumber() ); } } void CTFReplay::OnEndRecording() { if ( gameeventmanager ) { gameeventmanager->RemoveListener( this ); } BaseClass::OnEndRecording(); } void CTFReplay::OnComplete() { BaseClass::OnComplete(); } void CTFReplay::Update() { // If local player is medic and invuln'd someone, take a screenshot MedicUpdate(); BaseClass::Update(); } void CTFReplay::MedicUpdate() { // Not ready for update? if ( gpGlobals->curtime < m_flNextMedicUpdateTime ) return; // Local player doesn't exist? C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) { Assert( 0 ); // Shouldn't happen return; } if ( pLocalPlayer->GetPlayerClass()->GetClassIndex() != TF_CLASS_MEDIC ) return; // Releasing charge? if ( pLocalPlayer->MedicIsReleasingCharge() ) { // Take a sick screenshot CaptureScreenshotParams_t params; V_memset( ¶ms, 0, sizeof( params ) ); params.m_flDelay = 0.25f; g_pReplayScreenshotManager->CaptureScreenshot( params ); // Set next update to minimum time it would be until next recharge extern ConVar weapon_medigun_chargerelease_rate; m_flNextMedicUpdateTime = gpGlobals->curtime + weapon_medigun_chargerelease_rate.GetFloat(); } else { // Check again in a second m_flNextMedicUpdateTime = gpGlobals->curtime + 1.0f; } } float CTFReplay::GetSentryKillScreenshotDelay() { ConVarRef replay_screenshotsentrykilldelay( "replay_screenshotsentrykilldelay" ); return replay_screenshotsentrykilldelay.IsValid() ? replay_screenshotsentrykilldelay.GetFloat() : 0.5f; } void CTFReplay::FireGameEvent( IGameEvent *pEvent ) { C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return; CaptureScreenshotParams_t params; V_memset( ¶ms, 0, sizeof( params ) ); if ( FStrEq( pEvent->GetName(), "player_death" ) ) { ConVarRef replay_debug( "replay_debug" ); if ( replay_debug.IsValid() && replay_debug.GetBool() ) { DevMsg( "%i: CTFReplay::FireGameEvent(): player_death\n", gpGlobals->tickcount ); } int nKillerID = pEvent->GetInt( "attacker" ); int nVictimID = pEvent->GetInt( "userid" ); int nDeathFlags = pEvent->GetInt( "death_flags" ); int nAssisterID = pEvent->GetInt( "assister" ); const char *pWeaponName = pEvent->GetString( "weapon" ); // Suicide? bool bSuicide = nKillerID == nVictimID; // Try to get killer C_TFPlayer *pKiller = ToTFPlayer( USERID2PLAYER( nKillerID ) ); // Try to get victim C_TFPlayer *pVictim = ToTFPlayer( USERID2PLAYER( nVictimID ) ); // Is local player healing the killer? bool bKillerLastHealerIsLocalPlayer = pKiller && pKiller->GetWasHealedByLocalPlayer(); // Inflictor was a sentry gun? bool bSentry = V_strnicmp( pWeaponName, "obj_sentrygun", 13 ) == 0; int nInflictorEntIndex = pEvent->GetInt( "inflictor_entindex" ); C_BaseEntity *pInflictor = ClientEntityList().GetEnt( nInflictorEntIndex ); C_ObjectSentrygun *pSentry = dynamic_cast< C_ObjectSentrygun * >( pInflictor ); bool bFeignDeath = pEvent->GetInt( "death_flags" ) & TF_DEATH_FEIGN_DEATH; // Is the killer the local player? if ( nKillerID == pLocalPlayer->GetUserID() && !bSuicide && !bSentry ) { // Domination? if ( nDeathFlags & TF_DEATH_DOMINATION ) { AddDomination( nVictimID ); } // Assister domination? if ( ( nDeathFlags & TF_DEATH_ASSISTER_DOMINATION ) && ( nAssisterID > 0 ) ) { AddAssisterDomination( nVictimID, nAssisterID ); } // Revenge? if ( pEvent->GetInt( "death_flags" ) & TF_DEATH_REVENGE ) { AddRevenge( nVictimID ); } // Assister revenge? if ( pEvent->GetInt( "death_flags" ) & TF_DEATH_ASSISTER_REVENGE && ( nAssisterID > 0 ) ) { AddAssisterRevenge( nVictimID, nAssisterID ); } // Add victim info to kill list if ( pVictim ) { AddKill( pVictim->GetPlayerName(), pVictim->GetPlayerClass()->GetClassIndex() ); } // Take a quick screenshot with some delay ConVarRef replay_screenshotkilldelay( "replay_screenshotkilldelay" ); if ( replay_screenshotkilldelay.IsValid() ) { params.m_flDelay = GetKillScreenshotDelay(); g_pReplayScreenshotManager->CaptureScreenshot( params ); } } // Player death? else if ( pKiller && nVictimID == pLocalPlayer->GetUserID() ) { // Record who killed the player if not a suicide if ( !bSuicide && !bFeignDeath ) { RecordPlayerDeath( pKiller->GetPlayerName(), pKiller->GetPlayerClass()->GetClassIndex() ); } // Take screenshot - taking a screenshot during feign death is cool, too. ConVarRef replay_deathcammaxverticaloffset( "replay_deathcammaxverticaloffset" ); ConVarRef replay_playerdeathscreenshotdelay( "replay_playerdeathscreenshotdelay" ); params.m_flDelay = replay_playerdeathscreenshotdelay.IsValid() ? replay_playerdeathscreenshotdelay.GetFloat() : 0.0f; params.m_nEntity = pLocalPlayer->entindex(); params.m_posCamera.Init( 0,0, replay_deathcammaxverticaloffset.IsValid() ? replay_deathcammaxverticaloffset.GetFloat() : 150 ); params.m_angCamera.Init( 90, 0, 0 ); // Look straight down params.m_bUseCameraAngles = true; params.m_bIgnoreMinTimeBetweenScreenshots = true; g_pReplayScreenshotManager->CaptureScreenshot( params ); } // Killer is invuln/crit boosted and healer is local player? else if ( bKillerLastHealerIsLocalPlayer && ( pKiller->m_Shared.IsCritBoosted() || pKiller->m_Shared.InCond( TF_COND_INVULNERABLE ) || pKiller->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) ) ) { // Take a quick screenshot with some delay params.m_flDelay = GetKillScreenshotDelay(); g_pReplayScreenshotManager->CaptureScreenshot( params ); } // Is the inflictor a sentry belonging to the local player? else if ( pLocalPlayer->IsAlive() && bSentry && pSentry && pSentry->GetOwner() == pLocalPlayer && pVictim ) { ConVarRef replay_sentrycammaxverticaloffset( "replay_sentrycammaxverticaloffset" ); ConVarRef replay_sentrycamoffset_frontback( "replay_sentrycamoffset_frontback" ); ConVarRef replay_sentrycamoffset_leftright( "replay_sentrycamoffset_leftright" ); ConVarRef replay_sentrycamoffset_updown( "replay_sentrycamoffset_updown" ); // Setup screenshot params params.m_flDelay = GetSentryKillScreenshotDelay(); params.m_nEntity = pSentry->entindex(); params.m_bUseCameraAngles = true; // Calculate camera eye position static float s_aSentryEyeLevels[3] = { SENTRYGUN_EYE_OFFSET_LEVEL_1[2], SENTRYGUN_EYE_OFFSET_LEVEL_2[2], SENTRYGUN_EYE_OFFSET_LEVEL_3[2] }; int iSentryUpgrade = clamp( pSentry->GetUpgradeLevel() - 1, 0, 2 ); Vector vecSentryEyeOffset = Vector( 0, 0, s_aSentryEyeLevels[ iSentryUpgrade ] ); Vector vecSentryAimDir; // Since it seems the sentry's *actual* aim direction is only available on the server, use the victim's location to calculate a general idea of one Vector vecVictimUp = pVictim->WorldSpaceCenter() - pVictim->GetAbsOrigin(); // WorldSpaceCenter() seems to return player's eye level Vector vecVictimCenter = pVictim->GetAbsOrigin() + 0.5f * vecVictimUp; vecSentryAimDir = vecVictimCenter - pSentry->GetAbsOrigin() + vecSentryEyeOffset; VectorNormalizeFast( vecSentryAimDir ); Vector vecX, vecY, vecZ; // Construct a matrix to transform the eye point vecX = vecSentryAimDir; vecY = CrossProduct( Vector(0,0,1), vecSentryAimDir ); vecZ = CrossProduct( vecX, vecY ); matrix3x4_t m; m.Init( vecX, vecY, vecZ, vec3_origin ); Vector out; // Transform the point relative to the sentry's eye Vector vecOffset; if ( replay_sentrycamoffset_frontback.IsValid() && replay_sentrycamoffset_leftright.IsValid() && replay_sentrycamoffset_updown.IsValid() ) { vecOffset.Init( replay_sentrycamoffset_frontback.GetFloat(), -replay_sentrycamoffset_leftright.GetFloat(), replay_sentrycamoffset_updown.GetFloat() ); } else { vecOffset.Init( 0, 0, 0 ); } VectorTransform( vecOffset, m, out ); out += Vector( 0,0, s_aSentryEyeLevels[ iSentryUpgrade ] + ( replay_sentrycammaxverticaloffset.IsValid() ? replay_sentrycammaxverticaloffset.GetFloat() : 5 ) ); params.m_posCamera = out; // Use the aim matrix we constructed as the camera's orientation MatrixAngles( m, params.m_angCamera ); // Take the screenshot from the sentry's point of view g_pReplayScreenshotManager->CaptureScreenshot( params ); } } } bool CTFReplay::IsValidClass( int nClass ) const { return IsValidTFPlayerClass( nClass ); } bool CTFReplay::IsValidTeam( int iTeam ) const { return IsValidTFTeam( iTeam ); } bool CTFReplay::GetCurrentStats( RoundStats_t &out ) { if ( !g_TF_PR ) return false; C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return false; int iLocalPlayerIndex = GetLocalPlayerIndex(); out.m_iStat[ TFSTAT_POINTSSCORED ] = g_TF_PR->GetPlayerScore( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_DEATHS ] = g_TF_PR->GetDeaths( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_CAPTURES ] = pLocalPlayer->m_Shared.GetCaptures( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_DEFENSES ] = pLocalPlayer->m_Shared.GetDefenses( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_DOMINATIONS ] = pLocalPlayer->m_Shared.GetDominations( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_REVENGE ] = pLocalPlayer->m_Shared.GetRevenge( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_BUILDINGSDESTROYED ] = pLocalPlayer->m_Shared.GetBuildingsDestroyed( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_HEADSHOTS ] = pLocalPlayer->m_Shared.GetHeadshots( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_BACKSTABS ] = pLocalPlayer->m_Shared.GetBackstabs( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_HEALING ] = pLocalPlayer->m_Shared.GetHealPoints( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_INVULNS ] = pLocalPlayer->m_Shared.GetInvulns( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_TELEPORTS ] = pLocalPlayer->m_Shared.GetTeleports( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_KILLASSISTS ] = pLocalPlayer->m_Shared.GetKillAssists( iLocalPlayerIndex ); out.m_iStat[ TFSTAT_BONUS_POINTS ] = pLocalPlayer->m_Shared.GetBonusPoints( iLocalPlayerIndex ); return true; } const char *CTFReplay::GetStatString( int iStat ) const { COMPILE_TIME_ASSERT( TFSTAT_TOTAL == ARRAYSIZE( s_pStatStrings ) ); Assert( iStat >= TFSTAT_UNDEFINED && iStat < TFSTAT_TOTAL ); return ClampedArrayElement( s_pStatStrings, iStat ); } const char *CTFReplay::GetPlayerClass( int iClass ) const { COMPILE_TIME_ASSERT( TF_CLASS_MENU_BUTTONS == ARRAYSIZE( g_aPlayerClassNames_NonLocalized ) ); Assert( iClass >= TF_CLASS_UNDEFINED && iClass < TF_CLASS_COUNT ); return ClampedArrayElement( g_aPlayerClassNames_NonLocalized, iClass ); } bool CTFReplay::Read( KeyValues *pIn ) { return BaseClass::Read( pIn ); } void CTFReplay::Write( KeyValues *pOut ) { BaseClass::Write( pOut ); } const char *CTFReplay::GetMaterialFriendlyPlayerClass() const { const char *pPlayerClass = BaseClass::GetMaterialFriendlyPlayerClass(); if ( !V_stricmp( pPlayerClass, "heavyweapons" ) ) return "heavy"; else if ( !V_stricmp( pPlayerClass, "demoman" ) ) return "demo"; return pPlayerClass; } void CTFReplay::DumpGameSpecificData() const { BaseClass::DumpGameSpecificData(); } //---------------------------------------------------------------------------------------- class CTFReplayFactory : public IReplayFactory { public: virtual CReplay *Create() { return new CTFReplay(); } }; static CTFReplayFactory s_ReplayManager; IReplayFactory *g_pReplayFactory = &s_ReplayManager; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CTFReplayFactory, IReplayFactory, INTERFACE_VERSION_REPLAY_FACTORY, s_ReplayManager ); #endif