aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hltvdirector.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/hltvdirector.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/hltvdirector.cpp')
-rw-r--r--mp/src/game/server/hltvdirector.cpp2368
1 files changed, 1184 insertions, 1184 deletions
diff --git a/mp/src/game/server/hltvdirector.cpp b/mp/src/game/server/hltvdirector.cpp
index 926bf510..34d69736 100644
--- a/mp/src/game/server/hltvdirector.cpp
+++ b/mp/src/game/server/hltvdirector.cpp
@@ -1,1184 +1,1184 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-// $NoKeywords: $
-//
-//=============================================================================//
-// hltvdirector.cpp: implementation of the CHLTVDirector class.
-//
-//////////////////////////////////////////////////////////////////////
-
-#include "cbase.h"
-#include "hltvdirector.h"
-#include "KeyValues.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-//////////////////////////////////////////////////////////////////////
-// Construction/Destruction
-//////////////////////////////////////////////////////////////////////
-
-
-static ConVar tv_delay( "tv_delay", "30", 0, "SourceTV broadcast delay in seconds", true, 0, true, HLTV_MAX_DELAY );
-static ConVar tv_allow_static_shots( "tv_allow_static_shots", "1", 0, "Auto director uses fixed level cameras for shots" );
-static ConVar tv_allow_camera_man( "tv_allow_camera_man", "1", 0, "Auto director allows spectators to become camera man" );
-
-static bool GameEventLessFunc( CHLTVGameEvent const &e1, CHLTVGameEvent const &e2 )
-{
- return e1.m_Tick < e2.m_Tick;
-}
-
-#define RANDOM_MAX_ELEMENTS 256
-static int s_RndOrder[RANDOM_MAX_ELEMENTS];
-static void InitRandomOrder(int nFields)
-{
- if ( nFields > RANDOM_MAX_ELEMENTS )
- {
- Assert( nFields > RANDOM_MAX_ELEMENTS );
- nFields = RANDOM_MAX_ELEMENTS;
- }
-
- for ( int i=0; i<nFields; i++ )
- {
- s_RndOrder[i]=i;
- }
-
- for ( int i=0; i<(nFields/2); i++ )
- {
- int pos1 = RandomInt( 0, nFields-1 );
- int pos2 = RandomInt( 0, nFields-1 );
- int temp = s_RndOrder[pos1];
- s_RndOrder[pos1] = s_RndOrder[pos2];
- s_RndOrder[pos2] = temp;
- }
-};
-
-
-static float WeightedAngle( Vector vec1, Vector vec2)
-{
- VectorNormalize( vec1 );
- VectorNormalize( vec2 );
-
- float a = DotProduct( vec1, vec2 ); // a = [-1,1]
-
- a = (a + 1.0f) / 2.0f;
-
- Assert ( a <= 1 && a >= 0 );
-
- return a*a; // vectors are facing opposite direction
-}
-
-#if !defined( CSTRIKE_DLL ) && !defined( DOD_DLL ) && !defined( TF_DLL )// add your mod here if you use your own director
-
-static CHLTVDirector s_HLTVDirector; // singleton
-
-EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CHLTVDirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR, s_HLTVDirector );
-
-CHLTVDirector* HLTVDirector()
-{
- return &s_HLTVDirector;
-}
-
-IGameSystem* HLTVDirectorSystem()
-{
- return &s_HLTVDirector;
-}
-
-#endif // MODs
-
-
-
-CHLTVDirector::CHLTVDirector()
-{
- m_iPVSEntity = 0;
- m_fDelay = 30.0;
- m_iLastPlayer = 1;
- m_pHLTVServer = NULL;
- m_pHLTVClient = NULL;
- m_iCameraMan = 0;
- m_nNumFixedCameras = 0;
- m_EventHistory.SetLessFunc( GameEventLessFunc );
- m_nNextAnalyzeTick = 0;
- m_iCameraManIndex = 0;
-}
-
-CHLTVDirector::~CHLTVDirector()
-{
-
-}
-
-bool CHLTVDirector::Init()
-{
- return gameeventmanager->LoadEventsFromFile( "resource/hltvevents.res" ) > 0;
-}
-
-void CHLTVDirector::Shutdown()
-{
- RemoveEventsFromHistory(-1); // all
-}
-
-void CHLTVDirector::FireGameEvent( IGameEvent * event )
-{
- if ( !m_pHLTVServer )
- return; // don't do anything
-
- CHLTVGameEvent gameevent;
-
- gameevent.m_Event = gameeventmanager->DuplicateEvent( event );
- gameevent.m_Priority = event->GetInt( "priority", -1 ); // priorities are leveled between 0..10, -1 means ignore
- gameevent.m_Tick = gpGlobals->tickcount;
-
- m_EventHistory.Insert( gameevent );
-}
-
-IHLTVServer* CHLTVDirector::GetHLTVServer( void )
-{
- return m_pHLTVServer;
-}
-
-void CHLTVDirector::SetHLTVServer( IHLTVServer *hltv )
-{
- RemoveEventsFromHistory(-1); // all
-
- if ( hltv )
- {
- m_pHLTVClient = UTIL_PlayerByIndex( hltv->GetHLTVSlot() + 1 );
-
- if ( m_pHLTVClient && m_pHLTVClient->IsHLTV() )
- {
- m_pHLTVServer = hltv;
- }
- else
- {
- m_pHLTVServer = NULL;
- Error( "Couldn't find HLTV client player." );
- }
-
- // register for events the director needs to know
- ListenForGameEvent( "player_hurt" );
- ListenForGameEvent( "player_death" );
- ListenForGameEvent( "round_end" );
- ListenForGameEvent( "round_start" );
- ListenForGameEvent( "hltv_cameraman" );
- ListenForGameEvent( "hltv_rank_entity" );
- ListenForGameEvent( "hltv_rank_camera" );
- }
- else
- {
- // deactivate HLTV director
- m_pHLTVServer = NULL;
- }
-}
-
-bool CHLTVDirector::IsActive( void )
-{
- return (m_pHLTVServer != NULL );
-}
-
-float CHLTVDirector::GetDelay( void )
-{
- return m_fDelay;
-}
-
-int CHLTVDirector::GetDirectorTick( void )
-{
- // just simple delay it
- return m_nBroadcastTick;
-}
-
-int CHLTVDirector::GetPVSEntity( void )
-{
- return m_iPVSEntity;
-}
-
-Vector CHLTVDirector::GetPVSOrigin( void )
-{
- return m_vPVSOrigin;
-}
-
-void CHLTVDirector::UpdateSettings()
-{
- // set delay
- m_fDelay = tv_delay.GetFloat();
-
- int newBroadcastTick = gpGlobals->tickcount;
-
- if ( m_fDelay < HLTV_MIN_DIRECTOR_DELAY )
- {
- // instant broadcast, no delay
- m_fDelay = 0.0;
- }
- else
- {
- // broadcast time is current time - delay time
- newBroadcastTick -= TIME_TO_TICKS( m_fDelay );
- }
-
- if( (m_nBroadcastTick == 0) && (newBroadcastTick > 0) )
- {
- // we start broadcasting right now, reset NextShotTimer
- m_nNextShotTick = 0;
- }
-
- // check if camera man is still valid
- if ( m_iCameraManIndex > 0 )
- {
- CBasePlayer *pPlayer = UTIL_PlayerByIndex( m_iCameraManIndex );
- if ( !pPlayer || pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
- {
- SetCameraMan( 0 );
- }
- }
-
- m_nBroadcastTick = MAX( 0, newBroadcastTick );
-}
-
-const char** CHLTVDirector::GetModEvents()
-{
- static const char *s_modevents[] =
- {
- "hltv_status",
- "hltv_chat",
- "player_connect",
- "player_disconnect",
- "player_team",
- "player_info",
- "server_cvar",
- "player_death",
- "player_chat",
- "round_start",
- "round_end",
- NULL
- };
-
- return s_modevents;
-}
-
-
-void CHLTVDirector::BuildCameraList( void )
-{
- m_nNumFixedCameras = 0;
- memset( m_pFixedCameras, 0, sizeof ( m_pFixedCameras ) );
-
- CBaseEntity *pCamera = gEntList.FindEntityByClassname( NULL, GetFixedCameraEntityName() );
-
- while ( pCamera && m_nNumFixedCameras < MAX_NUM_CAMERAS)
- {
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, STRING(pCamera->m_target) );
-
- if ( pTarget )
- {
- // look at target if any given
- QAngle angles;
- VectorAngles( pTarget->GetAbsOrigin() - pCamera->GetAbsOrigin(), angles );
- pCamera->SetAbsAngles( angles );
- }
-
- m_pFixedCameras[m_nNumFixedCameras] = pCamera;
-
- m_nNumFixedCameras++;
- pCamera = gEntList.FindEntityByClassname( pCamera, GetFixedCameraEntityName() );
- }
-}
-
-// this is called with every new map
-void CHLTVDirector::LevelInitPostEntity( void )
-{
- BuildCameraList();
-
- m_vPVSOrigin.Init();
- m_iPVSEntity = 0;
- m_nNextShotTick = 0;
- m_nNextAnalyzeTick = 0;
- m_iCameraManIndex = 0;
-
- RemoveEventsFromHistory(-1); // all
-
- // DevMsg("HLTV Director: found %i fixed cameras.\n", m_nNumFixedCameras );
-}
-
-void CHLTVDirector::FrameUpdatePostEntityThink( void )
-{
- if ( !m_pHLTVServer )
- return; // don't do anything
-
- // This function is called each tick
- UpdateSettings(); // update settings from cvars
-
- if ( (m_nNextAnalyzeTick < gpGlobals->tickcount) &&
- (m_fDelay >= HLTV_MIN_DIRECTOR_DELAY) )
- {
- m_nNextAnalyzeTick = gpGlobals->tickcount + TIME_TO_TICKS( 0.5f );
-
- AnalyzePlayers();
-
- AnalyzeCameras();
- }
-
- if ( m_nBroadcastTick <= 0 )
- {
- // game start is still in delay loop
- StartDelayMessage();
- }
- else if ( m_nNextShotTick <= m_nBroadcastTick )
- {
- // game is being broadcasted, generate camera shots
- StartNewShot();
- }
-}
-
-void CHLTVDirector::StartDelayMessage()
-{
- if ( m_nNextShotTick > gpGlobals->tickcount )
- return;
-
- // check the next 8 seconds for interrupts/important events
- m_nNextShotTick = gpGlobals->tickcount + TIME_TO_TICKS( DEF_SHOT_LENGTH );
-
- // game hasn't started yet, we are still in the broadcast delay hole
- IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_message", true );
-
- if ( msg )
- {
- msg->SetString("text", "Please wait for broadcast to start ..." );
-
- // send spectators the HLTV director command as a game event
- m_pHLTVServer->BroadcastEvent( msg );
- gameeventmanager->FreeEvent( msg );
- }
-
- StartBestFixedCameraShot( true );
-}
-
-void CHLTVDirector::StartBestPlayerCameraShot()
-{
- float flPlayerRanking[MAX_PLAYERS];
-
- memset( flPlayerRanking, 0, sizeof(flPlayerRanking) );
-
- int firstIndex = FindFirstEvent( m_nBroadcastTick );
-
- int index = firstIndex;
-
- float flBestRank = -1.0f;
- int iBestCamera = -1;
- int iBestTarget = -1;
-
- // sum all ranking values for the cameras
-
- while( index != m_EventHistory.InvalidIndex() )
- {
- CHLTVGameEvent &dc = m_EventHistory[index];
-
- if ( dc.m_Tick >= m_nNextShotTick )
- break;
-
- // search for camera ranking events
- if ( Q_strcmp( dc.m_Event->GetName(), "hltv_rank_entity") == 0 )
- {
- int index = dc.m_Event->GetInt("index");
-
- if ( index < MAX_PLAYERS )
- {
- flPlayerRanking[index] += dc.m_Event->GetFloat("rank" );
-
- // find best camera
- if ( flPlayerRanking[index] > flBestRank )
- {
- iBestCamera = index;
- flBestRank = flPlayerRanking[index];
- iBestTarget = dc.m_Event->GetInt("target");
- }
- }
- }
-
- index = m_EventHistory.NextInorder( index );
- }
-
- if ( iBestCamera != -1 )
- {
- // view over shoulder, randomly left or right
- StartChaseCameraShot( iBestCamera, iBestTarget, 112.0f, 20, (RandomFloat()>0.5)?20:-20, false );
- }
- else
- {
- StartBestFixedCameraShot( true );
- }
-}
-
-void CHLTVDirector::StartFixedCameraShot(int iCamera, int iTarget)
-{
- CBaseEntity *pCamera = m_pFixedCameras[iCamera];
-
-
- Vector vCamPos = pCamera->GetAbsOrigin();
- QAngle aViewAngle = pCamera->GetAbsAngles();
-
- m_iPVSEntity = 0; // don't use camera entity, since it may not been transmitted
- m_vPVSOrigin = vCamPos;
-
- IGameEvent *shot = gameeventmanager->CreateEvent( "hltv_fixed", true );
-
- if ( shot )
- {
- shot->SetInt("posx", vCamPos.x );
- shot->SetInt("posy", vCamPos.y );
- shot->SetInt("posz", vCamPos.z );
- shot->SetInt("theta", aViewAngle.x );
- shot->SetInt("phi", aViewAngle.y );
- shot->SetInt("target", iTarget );
- shot->SetFloat("fov", RandomFloat(50,110) );
-
- // send spectators the HLTV director command as a game event
- m_pHLTVServer->BroadcastEvent( shot );
- gameeventmanager->FreeEvent( shot );
- }
-}
-
-void CHLTVDirector::StartChaseCameraShot(int iTarget1, int iTarget2, int distance, int phi, int theta, bool bInEye)
-{
- IGameEvent *shot = gameeventmanager->CreateEvent( "hltv_chase", true );
-
- if ( !shot )
- return;
-
- shot->SetInt("target1", iTarget1 );
- shot->SetInt("target2", iTarget2 );
- shot->SetInt("distance", distance );
- shot->SetInt("phi", phi ); // hi/low
- shot->SetInt( "theta", theta ); // left/right
- shot->SetInt( "ineye", bInEye?1:0 );
-
- m_iPVSEntity = iTarget1;
-
- // send spectators the HLTV director command as a game event
- m_pHLTVServer->BroadcastEvent( shot );
- gameeventmanager->FreeEvent( shot );
-}
-
-void CHLTVDirector::StartBestFixedCameraShot( bool bForce )
-{
- float flCameraRanking[MAX_NUM_CAMERAS];
-
- if ( m_nNumFixedCameras <= 0 )
- return;
-
- memset( flCameraRanking, 0, sizeof(flCameraRanking) );
-
- int firstIndex = FindFirstEvent( m_nBroadcastTick );
-
- int index = firstIndex;
-
- float flBestRank = -1.0f;
- int iBestCamera = -1;
- int iBestTarget = -1;
-
- // sum all ranking values for the cameras
-
- while( index != m_EventHistory.InvalidIndex() )
- {
- CHLTVGameEvent &dc = m_EventHistory[index];
-
- if ( dc.m_Tick >= m_nNextShotTick )
- break;
-
- // search for camera ranking events
- if ( Q_strcmp( dc.m_Event->GetName(), "hltv_rank_camera") == 0 )
- {
- int index = dc.m_Event->GetInt("index");
- flCameraRanking[index] += dc.m_Event->GetFloat("rank" );
-
- // find best camera
- if ( flCameraRanking[index] > flBestRank )
- {
- iBestCamera = index;
- flBestRank = flCameraRanking[index];
- iBestTarget = dc.m_Event->GetInt("target");
- }
- }
-
- index = m_EventHistory.NextInorder( index );
- }
-
- if ( !bForce && flBestRank == 0 )
- {
- // if we are not forcing a fixed camera shot, switch to player chase came
- // if no camera shows any players
- StartBestPlayerCameraShot();
- }
- else if ( iBestCamera != -1 )
- {
- StartFixedCameraShot( iBestCamera, iBestTarget );
- }
-}
-
-void CHLTVDirector::StartRandomShot()
-{
- int toTick = m_nBroadcastTick + TIME_TO_TICKS ( DEF_SHOT_LENGTH );
- m_nNextShotTick = MIN( m_nNextShotTick, toTick );
-
- if ( RandomFloat(0,1) < 0.25 && tv_allow_static_shots.GetBool() )
- {
- // create a static shot from a level camera
- StartBestFixedCameraShot( false );
- }
- else
- {
- // follow a player
- StartBestPlayerCameraShot();
- }
-}
-
-void CHLTVDirector::CreateShotFromEvent( CHLTVGameEvent *event )
-{
- // show event at least for 2 more seconds after it occured
- const char *name = event->m_Event->GetName();
-
- bool bPlayerHurt = Q_strcmp( "player_hurt", name ) == 0;
- bool bPlayerKilled = Q_strcmp( "player_death", name ) == 0;
- bool bRoundStart = Q_strcmp( "round_start", name ) == 0;
- bool bRoundEnd = Q_strcmp( "round_end", name ) == 0;
-
- if ( bPlayerHurt || bPlayerKilled )
- {
- CBaseEntity *victim = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
- CBaseEntity *attacker = UTIL_PlayerByUserId( event->m_Event->GetInt("attacker") );
-
- if ( !victim )
- return;
-
- if ( attacker == victim || attacker == NULL )
- {
- // player killed self or by WORLD
- StartChaseCameraShot( victim->entindex(), 0, 96, 20, 0, false );
- }
- else // attacker != NULL
- {
- // check if we would show it from ineye view
- bool bInEye = (bPlayerKilled && RandomFloat(0,1) > 0.33) || (bPlayerHurt && RandomFloat(0,1) > 0.66);
-
- // if we show ineye view, show it more likely from killer
- if ( RandomFloat(0,1) > (bInEye?0.3f:0.7f) )
- {
- ::V_swap( attacker, victim );
- }
-
- // hurting a victim is shown as chase more often
- // view from behind over head
- // lower view point, dramatic
- // view over shoulder, randomly left or right
- StartChaseCameraShot( victim->entindex(), attacker->entindex(), 96, -20, (RandomFloat()>0.5)?30:-30, bInEye );
- }
-
- // shot 2 seconds after death/hurt
- m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
- }
- else if ( bRoundStart || bRoundEnd )
- {
- StartBestFixedCameraShot( false );
- }
- else
- {
- DevMsg( "No known TV shot for event %s\n", name );
- }
-}
-
-void CHLTVDirector::CheckHistory()
-{
- int index = m_EventHistory.FirstInorder();
- int lastTick = -1;
-
- while ( index != m_EventHistory.InvalidIndex() )
- {
- CHLTVGameEvent &dc = m_EventHistory[index];
-
- Assert( lastTick <= dc.m_Tick );
- lastTick = dc.m_Tick;
-
- index = m_EventHistory.NextInorder( index );
- }
-}
-
-void CHLTVDirector::RemoveEventsFromHistory(int tick)
-{
- int index = m_EventHistory.FirstInorder();
-
- while ( index != m_EventHistory.InvalidIndex() )
- {
- CHLTVGameEvent &dc = m_EventHistory[index];
-
- if ( (dc.m_Tick < tick) || (tick == -1) )
- {
- gameeventmanager->FreeEvent( dc.m_Event );
- dc.m_Event = NULL;
- m_EventHistory.RemoveAt( index );
- index = m_EventHistory.FirstInorder(); // start again
- }
- else
- {
- index = m_EventHistory.NextInorder( index );
- }
- }
-
-#ifdef _DEBUG
- CheckHistory();
-#endif
-}
-
-int CHLTVDirector::FindFirstEvent( int tick )
-{
- // TODO cache last queried ticks
-
- int index = m_EventHistory.FirstInorder();
-
- if ( index == m_EventHistory.InvalidIndex() )
- return index; // no commands in list
-
- CHLTVGameEvent *event = &m_EventHistory[index];
-
- while ( event->m_Tick < tick )
- {
- index = m_EventHistory.NextInorder( index );
-
- if ( index == m_EventHistory.InvalidIndex() )
- break;
-
- event = &m_EventHistory[index];
- }
-
- return index;
-}
-
-bool CHLTVDirector::SetCameraMan( int iPlayerIndex )
-{
- if ( !tv_allow_camera_man.GetBool() )
- return false;
-
- if ( m_iCameraManIndex == iPlayerIndex )
- return true;
-
- // check if somebody else is already the camera man
- if ( m_iCameraManIndex != 0 && iPlayerIndex != 0 )
- return false;
-
- CBasePlayer *pPlayer = NULL;
-
- if ( iPlayerIndex > 0 )
- {
- pPlayer = UTIL_PlayerByIndex( iPlayerIndex );
- if ( !pPlayer || pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
- return false;
- }
-
- m_iCameraManIndex = iPlayerIndex;
-
- // create event for director event history
- IGameEvent *event = gameeventmanager->CreateEvent( "hltv_cameraman" );
- if ( event )
- {
- event->SetInt("index", iPlayerIndex );
- gameeventmanager->FireEvent( event );
- }
-
- CRecipientFilter filter;
-
- for ( int i = 1; i <= gpGlobals->maxClients; i++ )
- {
- CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
-
- if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_SPECTATOR && !pPlayer->IsFakeClient() )
- {
- filter.AddRecipient( pPlayer );
- }
- }
-
- filter.MakeReliable();
-
- if ( iPlayerIndex > 0 )
- {
- // tell all spectators that the camera is in use.
- char szText[200];
- Q_snprintf( szText, sizeof(szText), "SourceTV camera is now controlled by %s.", pPlayer->GetPlayerName() );
- UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, szText );
- }
- else
- {
- // tell all spectators that the camera is available again.
- UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "SourceTV camera switched to auto-director mode." );
- }
-
- return true;
-}
-
-void CHLTVDirector::FinishCameraManShot()
-{
- Assert( m_iCameraMan == m_iPVSEntity );
-
- int index = FindFirstEvent( m_nBroadcastTick );
-
- if ( index == m_EventHistory.InvalidIndex() )
- {
- // check next frame again if event history is empty
- m_nNextShotTick = m_nBroadcastTick+1;
- return;
- }
-
- m_nNextShotTick = m_nBroadcastTick + TIME_TO_TICKS( MIN_SHOT_LENGTH );
-
- //check if camera turns camera off within broadcast time and game time
- while( index != m_EventHistory.InvalidIndex() )
- {
- CHLTVGameEvent &dc = m_EventHistory[index];
-
- if ( dc.m_Tick >= m_nNextShotTick )
- break;
-
- if ( Q_strcmp( dc.m_Event->GetName(), "hltv_cameraman") == 0 )
- {
- int iNewCameraMan = dc.m_Event->GetInt("index");
-
- if ( iNewCameraMan == 0 )
- {
- // camera man switched camera off
- m_nNextShotTick = dc.m_Tick+1;
- m_iCameraMan = 0;
- return;
- }
- }
-
- index = m_EventHistory.NextInorder( index );
- }
-
- // camera man is still recording and live, resend camera man message
- IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_cameraman", true );
- if ( msg )
- {
- msg->SetInt("index", m_iCameraMan );
- m_pHLTVServer->BroadcastEvent( msg );
- gameeventmanager->FreeEvent( msg );
- }
-
-}
-
-bool CHLTVDirector::StartCameraManShot()
-{
- Assert( m_nNextShotTick <= m_nBroadcastTick );
-
- int index = FindFirstEvent( m_nNextShotTick );
-
- // check for cameraman mode
- while( index != m_EventHistory.InvalidIndex() )
- {
- CHLTVGameEvent &dc = m_EventHistory[index];
-
- // only check if this is the current tick
- if ( dc.m_Tick > m_nBroadcastTick )
- break;
-
- if ( Q_strcmp( dc.m_Event->GetName(), "hltv_cameraman") == 0 )
- {
- if ( dc.m_Event->GetInt("index") > 0 )
- {
- // ok, this guy is now the active camera man
- m_iCameraMan = dc.m_Event->GetInt("index");
-
- m_iPVSEntity = m_iCameraMan;
- m_nNextShotTick = m_nBroadcastTick+1; // check setting right on next frame
-
- // send camera man command to client
- m_pHLTVServer->BroadcastEvent( dc.m_Event );
- return true;
- }
- }
-
- index = m_EventHistory.NextInorder( index );
- }
-
- return false; // no camera man found
-}
-
-void CHLTVDirector::StartInstantBroadcastShot()
-{
- m_nNextShotTick = m_nBroadcastTick + TIME_TO_TICKS( MAX_SHOT_LENGTH );
-
- if ( m_iCameraManIndex > 0 )
- {
- // camera man is still recording and live, resend camera man message
- IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_cameraman", true );
- if ( msg )
- {
- msg->SetInt("index", m_iCameraManIndex );
- m_pHLTVServer->BroadcastEvent( msg );
- gameeventmanager->FreeEvent( msg );
-
- m_iPVSEntity = m_iCameraManIndex;
- m_nNextShotTick = m_nBroadcastTick+TIME_TO_TICKS( MIN_SHOT_LENGTH );
- }
- }
- else
- {
- RemoveEventsFromHistory(-1); // all
-
- AnalyzePlayers();
-
- AnalyzeCameras();
-
- StartRandomShot();
- }
-}
-
-void CHLTVDirector::StartNewShot()
-{
- // we can remove all events the
- int smallestTick = MAX(0, gpGlobals->tickcount - TIME_TO_TICKS(HLTV_MAX_DELAY) );
- RemoveEventsFromHistory( smallestTick );
-
- // if the delay time is to short for autodirector, just show next best thing
- if ( m_fDelay < HLTV_MIN_DIRECTOR_DELAY )
- {
- StartInstantBroadcastShot();
- return;
- }
-
- if ( m_iCameraMan > 0 )
- {
- // we already have an active camera man,
- // wait until he releases the "record" lock
- FinishCameraManShot();
- return;
- }
-
- if ( StartCameraManShot() )
- {
- // now we have an active camera man
- return;
- }
-
- // ok, no camera man active, now check how much time
- // we have for the next shot, if the time diff to the next
- // important event we have to switch to is too short (<2sec)
- // just extent the current shot and don't start a new one
-
- // check the next 8 seconds for interrupts/important events
- m_nNextShotTick = m_nBroadcastTick + TIME_TO_TICKS( MAX_SHOT_LENGTH );
-
- if ( m_nBroadcastTick <= 0 )
- {
- // game hasn't started yet, we are still in the broadcast delay hole
- IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_message", true );
-
- if ( msg )
- {
- msg->SetString("text", "Please wait for broadcast to start ..." );
-
- // send spectators the HLTV director command as a game event
- m_pHLTVServer->BroadcastEvent( msg );
- gameeventmanager->FreeEvent( msg );
- }
-
- StartBestFixedCameraShot( true );
- return;
- }
-
- int index = FindFirstEvent( m_nBroadcastTick );
-
- while( index != m_EventHistory.InvalidIndex() )
- {
- CHLTVGameEvent &dc = m_EventHistory[index];
-
- if ( dc.m_Tick >= m_nNextShotTick )
- break; // we have searched enough
-
- // a camera man is always interrupting auto director
- if ( Q_strcmp( dc.m_Event->GetName(), "hltv_cameraman") == 0 )
- {
- if ( dc.m_Event->GetInt("index") > 0 )
- {
- // stop the next cut when this cameraman starts recording
- m_nNextShotTick = dc.m_Tick;
- break;
- }
- }
-
- index = m_EventHistory.NextInorder( index );
- }
-
- float flDuration = TICKS_TO_TIME(m_nNextShotTick - m_nBroadcastTick);
-
- if ( flDuration < MIN_SHOT_LENGTH )
- return; // not enough time for a new shot
-
- // find the most interesting game event for next shot
- CHLTVGameEvent *dc = FindBestGameEvent();
-
- if ( dc )
- {
- // show the game event
- CreateShotFromEvent( dc );
- }
- else
- {
- // no interesting events found, start random shot
- StartRandomShot();
- }
-}
-
-CHLTVGameEvent *CHLTVDirector::FindBestGameEvent()
-{
- int bestEvent[4];
- int bestEventPrio[4];
-
- Q_memset( bestEvent, 0, sizeof(bestEvent) );
- Q_memset( bestEventPrio, 0, sizeof(bestEventPrio) );
-
- int index = FindFirstEvent( m_nBroadcastTick );
-
- // search for next 4 best events within next 8 seconds
- for (int i = 0; i<4; i ++)
- {
- bestEventPrio[i] = 0;
- bestEvent[i] = 0;
-
- int tillTick = m_nBroadcastTick + TIME_TO_TICKS( 2.0f*(1.0f+i) );
-
- if ( tillTick > m_nNextShotTick )
- break;
-
- // sum all action for the next time
- while ( index != m_EventHistory.InvalidIndex() )
- {
- CHLTVGameEvent &event = m_EventHistory[index];
-
- if ( event.m_Tick > tillTick )
- break;
-
- int priority = event.m_Priority;
-
- if ( priority > bestEventPrio[i] )
- {
- bestEvent[i] = index;
- bestEventPrio[i] = priority;
- }
-
- index = m_EventHistory.NextInorder( index );
- }
- }
-
- if ( !( bestEventPrio[0] || bestEventPrio[1] || bestEventPrio[2] ) )
- return NULL; // no event found at all, give generic algorithm a chance
-
- // camera cut rules :
-
- if ( bestEventPrio[1] >= bestEventPrio[0] &&
- bestEventPrio[1] >= bestEventPrio[2] &&
- bestEventPrio[1] >= bestEventPrio[3] )
- {
- return &m_EventHistory[ bestEvent[1] ]; // best case
- }
- else if ( bestEventPrio[0] > bestEventPrio[1] &&
- bestEventPrio[0] > bestEventPrio[2] )
- {
- return &m_EventHistory[ bestEvent[0] ]; // event 0 is very important
- }
- else if ( bestEventPrio[2] > bestEventPrio[3] )
- {
- return &m_EventHistory[ bestEvent[2] ];
- }
- else
- {
- // event 4 is the best but too far away, so show event 1
- if ( bestEvent[0] )
- return &m_EventHistory[ bestEvent[0] ];
- else
- return NULL;
- }
-}
-
-void CHLTVDirector::AnalyzeCameras()
-{
- InitRandomOrder( m_nNumFixedCameras );
-
- for ( int i = 0; i<m_nNumFixedCameras; i++ )
- {
- int iCameraIndex = s_RndOrder[i];
- CBaseEntity *pCamera = m_pFixedCameras[ iCameraIndex ];
-
- float flRank = 0.0f;
- int iClosestPlayer = 0;
- float flClosestPlayerDist = 100000.0f;
- int nCount = 0; // Number of visible targets
- Vector vDistribution; vDistribution.Init(); // distribution of targets
-
- Vector vCamPos = pCamera->GetAbsOrigin();
-
- for ( int j=0; j<m_nNumActivePlayers; j++ )
- {
- CBasePlayer *pPlayer = m_pActivePlayers[j];
-
- Vector vPlayerPos = pPlayer->GetAbsOrigin();
-
- float dist = VectorLength( vPlayerPos - vCamPos );
-
- if ( dist > 1024.0f || dist < 4.0f )
- continue; // too colse or far away
-
- // check visibility
- trace_t tr;
- UTIL_TraceLine( vCamPos, pPlayer->GetAbsOrigin(), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction < 1.0 )
- continue; // not visible for camera
-
- nCount++;
-
- // remember closest player
- if ( dist < flClosestPlayerDist )
- {
- iClosestPlayer = pPlayer->entindex();
- flClosestPlayerDist = dist;
- }
-
- Vector v1; AngleVectors( pPlayer->EyeAngles(), &v1 );
-
- // check players orientation towards camera
- Vector v2 = vCamPos - vPlayerPos;
- VectorNormalize( v2 );
-
- // player/camera cost function:
- flRank += ( 1.0f/sqrt(dist) ) * WeightedAngle( v1, v2 );
-
- vDistribution += v2;
- }
-
- if ( nCount > 0 )
- {
- // normalize distribution
- flRank *= VectorLength( vDistribution ) / nCount;
- }
-
- IGameEvent *event = gameeventmanager->CreateEvent("hltv_rank_camera");
-
- if ( event )
- {
- event->SetFloat("rank", flRank );
- event->SetInt("index", iCameraIndex ); // index in m_pFixedCameras
- event->SetInt("target", iClosestPlayer ); // ent index
- gameeventmanager->FireEvent( event );
- }
- }
-}
-
-void CHLTVDirector::BuildActivePlayerList()
-{
- // first build list of all active players
-
- m_nNumActivePlayers = 0;
-
- for ( int i =1; i <= gpGlobals->maxClients; i++ )
- {
- CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
-
- if ( !pPlayer )
- continue;
-
- if ( !pPlayer->IsAlive() )
- continue;
-
- if ( pPlayer->IsObserver() )
- continue;
-
- if ( pPlayer->GetTeamNumber() <= TEAM_SPECTATOR )
- continue;
-
- m_pActivePlayers[m_nNumActivePlayers] = pPlayer;
- m_nNumActivePlayers++;
- }
-}
-
-void CHLTVDirector::AnalyzePlayers()
-{
- // build list of current active players
- BuildActivePlayerList();
-
- // analyzes every active player
-
- InitRandomOrder( m_nNumActivePlayers );
-
- for ( int i = 0; i<m_nNumActivePlayers; i++ )
- {
- int iPlayerIndex = s_RndOrder[i];
-
- CBasePlayer *pPlayer = m_pActivePlayers[ iPlayerIndex ];
-
- float flRank = 0.0f;
- int iBestFacingPlayer = 0;
- float flBestFacingPlayer = 0.0f;
- int nCount = 0; // Number of visible targets
- Vector vDistribution; vDistribution.Init(); // distribution of targets
-
- Vector vCamPos = pPlayer->GetAbsOrigin();
-
- Vector v1; AngleVectors( pPlayer->EyeAngles(), &v1 );
-
- v1 *= -1; // inverted
-
- for ( int j=0; j<m_nNumActivePlayers; j++ )
- {
- if ( iPlayerIndex == j )
- continue; // don't check against itself
-
- CBasePlayer *pOtherPlayer = m_pActivePlayers[j];
-
- Vector vPlayerPos = pOtherPlayer->GetAbsOrigin();
-
- float dist = VectorLength( vPlayerPos - vCamPos );
-
- if ( dist > 1024.0f || dist < 4.0f )
- continue; // too close or far away
-
- // check visibility
- trace_t tr;
- UTIL_TraceLine( vCamPos, pOtherPlayer->GetAbsOrigin(), MASK_SOLID, pOtherPlayer, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction < 1.0 )
- continue; // not visible for camera
-
- nCount++;
-
- // check players orientation towards camera
- Vector v2; AngleVectors( pOtherPlayer->EyeAngles(), &v2 );
-
- float facing = WeightedAngle( v1, v2 );
-
- // remember closest player
- if ( facing > flBestFacingPlayer )
- {
- iBestFacingPlayer = pOtherPlayer->entindex();
- flBestFacingPlayer = facing;
- }
-
- // player/camera cost function:
- flRank += ( 1.0f/sqrt(dist) ) * facing;
-
- vDistribution += v2;
- }
-
- if ( nCount > 0 )
- {
- float flDistribution = VectorLength( vDistribution ) / nCount; // normalize distribution
- flRank *= flDistribution;
- }
-
- IGameEvent *event = gameeventmanager->CreateEvent("hltv_rank_entity");
- if ( event )
- {
- event->SetInt("index", pPlayer->entindex() );
- event->SetFloat("rank", flRank );
- event->SetInt("target", iBestFacingPlayer ); // ent index
- gameeventmanager->FireEvent( event );
- }
- }
-}
-
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// hltvdirector.cpp: implementation of the CHLTVDirector class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "cbase.h"
+#include "hltvdirector.h"
+#include "KeyValues.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+
+static ConVar tv_delay( "tv_delay", "30", 0, "SourceTV broadcast delay in seconds", true, 0, true, HLTV_MAX_DELAY );
+static ConVar tv_allow_static_shots( "tv_allow_static_shots", "1", 0, "Auto director uses fixed level cameras for shots" );
+static ConVar tv_allow_camera_man( "tv_allow_camera_man", "1", 0, "Auto director allows spectators to become camera man" );
+
+static bool GameEventLessFunc( CHLTVGameEvent const &e1, CHLTVGameEvent const &e2 )
+{
+ return e1.m_Tick < e2.m_Tick;
+}
+
+#define RANDOM_MAX_ELEMENTS 256
+static int s_RndOrder[RANDOM_MAX_ELEMENTS];
+static void InitRandomOrder(int nFields)
+{
+ if ( nFields > RANDOM_MAX_ELEMENTS )
+ {
+ Assert( nFields > RANDOM_MAX_ELEMENTS );
+ nFields = RANDOM_MAX_ELEMENTS;
+ }
+
+ for ( int i=0; i<nFields; i++ )
+ {
+ s_RndOrder[i]=i;
+ }
+
+ for ( int i=0; i<(nFields/2); i++ )
+ {
+ int pos1 = RandomInt( 0, nFields-1 );
+ int pos2 = RandomInt( 0, nFields-1 );
+ int temp = s_RndOrder[pos1];
+ s_RndOrder[pos1] = s_RndOrder[pos2];
+ s_RndOrder[pos2] = temp;
+ }
+};
+
+
+static float WeightedAngle( Vector vec1, Vector vec2)
+{
+ VectorNormalize( vec1 );
+ VectorNormalize( vec2 );
+
+ float a = DotProduct( vec1, vec2 ); // a = [-1,1]
+
+ a = (a + 1.0f) / 2.0f;
+
+ Assert ( a <= 1 && a >= 0 );
+
+ return a*a; // vectors are facing opposite direction
+}
+
+#if !defined( CSTRIKE_DLL ) && !defined( DOD_DLL ) && !defined( TF_DLL )// add your mod here if you use your own director
+
+static CHLTVDirector s_HLTVDirector; // singleton
+
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CHLTVDirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR, s_HLTVDirector );
+
+CHLTVDirector* HLTVDirector()
+{
+ return &s_HLTVDirector;
+}
+
+IGameSystem* HLTVDirectorSystem()
+{
+ return &s_HLTVDirector;
+}
+
+#endif // MODs
+
+
+
+CHLTVDirector::CHLTVDirector()
+{
+ m_iPVSEntity = 0;
+ m_fDelay = 30.0;
+ m_iLastPlayer = 1;
+ m_pHLTVServer = NULL;
+ m_pHLTVClient = NULL;
+ m_iCameraMan = 0;
+ m_nNumFixedCameras = 0;
+ m_EventHistory.SetLessFunc( GameEventLessFunc );
+ m_nNextAnalyzeTick = 0;
+ m_iCameraManIndex = 0;
+}
+
+CHLTVDirector::~CHLTVDirector()
+{
+
+}
+
+bool CHLTVDirector::Init()
+{
+ return gameeventmanager->LoadEventsFromFile( "resource/hltvevents.res" ) > 0;
+}
+
+void CHLTVDirector::Shutdown()
+{
+ RemoveEventsFromHistory(-1); // all
+}
+
+void CHLTVDirector::FireGameEvent( IGameEvent * event )
+{
+ if ( !m_pHLTVServer )
+ return; // don't do anything
+
+ CHLTVGameEvent gameevent;
+
+ gameevent.m_Event = gameeventmanager->DuplicateEvent( event );
+ gameevent.m_Priority = event->GetInt( "priority", -1 ); // priorities are leveled between 0..10, -1 means ignore
+ gameevent.m_Tick = gpGlobals->tickcount;
+
+ m_EventHistory.Insert( gameevent );
+}
+
+IHLTVServer* CHLTVDirector::GetHLTVServer( void )
+{
+ return m_pHLTVServer;
+}
+
+void CHLTVDirector::SetHLTVServer( IHLTVServer *hltv )
+{
+ RemoveEventsFromHistory(-1); // all
+
+ if ( hltv )
+ {
+ m_pHLTVClient = UTIL_PlayerByIndex( hltv->GetHLTVSlot() + 1 );
+
+ if ( m_pHLTVClient && m_pHLTVClient->IsHLTV() )
+ {
+ m_pHLTVServer = hltv;
+ }
+ else
+ {
+ m_pHLTVServer = NULL;
+ Error( "Couldn't find HLTV client player." );
+ }
+
+ // register for events the director needs to know
+ ListenForGameEvent( "player_hurt" );
+ ListenForGameEvent( "player_death" );
+ ListenForGameEvent( "round_end" );
+ ListenForGameEvent( "round_start" );
+ ListenForGameEvent( "hltv_cameraman" );
+ ListenForGameEvent( "hltv_rank_entity" );
+ ListenForGameEvent( "hltv_rank_camera" );
+ }
+ else
+ {
+ // deactivate HLTV director
+ m_pHLTVServer = NULL;
+ }
+}
+
+bool CHLTVDirector::IsActive( void )
+{
+ return (m_pHLTVServer != NULL );
+}
+
+float CHLTVDirector::GetDelay( void )
+{
+ return m_fDelay;
+}
+
+int CHLTVDirector::GetDirectorTick( void )
+{
+ // just simple delay it
+ return m_nBroadcastTick;
+}
+
+int CHLTVDirector::GetPVSEntity( void )
+{
+ return m_iPVSEntity;
+}
+
+Vector CHLTVDirector::GetPVSOrigin( void )
+{
+ return m_vPVSOrigin;
+}
+
+void CHLTVDirector::UpdateSettings()
+{
+ // set delay
+ m_fDelay = tv_delay.GetFloat();
+
+ int newBroadcastTick = gpGlobals->tickcount;
+
+ if ( m_fDelay < HLTV_MIN_DIRECTOR_DELAY )
+ {
+ // instant broadcast, no delay
+ m_fDelay = 0.0;
+ }
+ else
+ {
+ // broadcast time is current time - delay time
+ newBroadcastTick -= TIME_TO_TICKS( m_fDelay );
+ }
+
+ if( (m_nBroadcastTick == 0) && (newBroadcastTick > 0) )
+ {
+ // we start broadcasting right now, reset NextShotTimer
+ m_nNextShotTick = 0;
+ }
+
+ // check if camera man is still valid
+ if ( m_iCameraManIndex > 0 )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( m_iCameraManIndex );
+ if ( !pPlayer || pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
+ {
+ SetCameraMan( 0 );
+ }
+ }
+
+ m_nBroadcastTick = MAX( 0, newBroadcastTick );
+}
+
+const char** CHLTVDirector::GetModEvents()
+{
+ static const char *s_modevents[] =
+ {
+ "hltv_status",
+ "hltv_chat",
+ "player_connect",
+ "player_disconnect",
+ "player_team",
+ "player_info",
+ "server_cvar",
+ "player_death",
+ "player_chat",
+ "round_start",
+ "round_end",
+ NULL
+ };
+
+ return s_modevents;
+}
+
+
+void CHLTVDirector::BuildCameraList( void )
+{
+ m_nNumFixedCameras = 0;
+ memset( m_pFixedCameras, 0, sizeof ( m_pFixedCameras ) );
+
+ CBaseEntity *pCamera = gEntList.FindEntityByClassname( NULL, GetFixedCameraEntityName() );
+
+ while ( pCamera && m_nNumFixedCameras < MAX_NUM_CAMERAS)
+ {
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, STRING(pCamera->m_target) );
+
+ if ( pTarget )
+ {
+ // look at target if any given
+ QAngle angles;
+ VectorAngles( pTarget->GetAbsOrigin() - pCamera->GetAbsOrigin(), angles );
+ pCamera->SetAbsAngles( angles );
+ }
+
+ m_pFixedCameras[m_nNumFixedCameras] = pCamera;
+
+ m_nNumFixedCameras++;
+ pCamera = gEntList.FindEntityByClassname( pCamera, GetFixedCameraEntityName() );
+ }
+}
+
+// this is called with every new map
+void CHLTVDirector::LevelInitPostEntity( void )
+{
+ BuildCameraList();
+
+ m_vPVSOrigin.Init();
+ m_iPVSEntity = 0;
+ m_nNextShotTick = 0;
+ m_nNextAnalyzeTick = 0;
+ m_iCameraManIndex = 0;
+
+ RemoveEventsFromHistory(-1); // all
+
+ // DevMsg("HLTV Director: found %i fixed cameras.\n", m_nNumFixedCameras );
+}
+
+void CHLTVDirector::FrameUpdatePostEntityThink( void )
+{
+ if ( !m_pHLTVServer )
+ return; // don't do anything
+
+ // This function is called each tick
+ UpdateSettings(); // update settings from cvars
+
+ if ( (m_nNextAnalyzeTick < gpGlobals->tickcount) &&
+ (m_fDelay >= HLTV_MIN_DIRECTOR_DELAY) )
+ {
+ m_nNextAnalyzeTick = gpGlobals->tickcount + TIME_TO_TICKS( 0.5f );
+
+ AnalyzePlayers();
+
+ AnalyzeCameras();
+ }
+
+ if ( m_nBroadcastTick <= 0 )
+ {
+ // game start is still in delay loop
+ StartDelayMessage();
+ }
+ else if ( m_nNextShotTick <= m_nBroadcastTick )
+ {
+ // game is being broadcasted, generate camera shots
+ StartNewShot();
+ }
+}
+
+void CHLTVDirector::StartDelayMessage()
+{
+ if ( m_nNextShotTick > gpGlobals->tickcount )
+ return;
+
+ // check the next 8 seconds for interrupts/important events
+ m_nNextShotTick = gpGlobals->tickcount + TIME_TO_TICKS( DEF_SHOT_LENGTH );
+
+ // game hasn't started yet, we are still in the broadcast delay hole
+ IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_message", true );
+
+ if ( msg )
+ {
+ msg->SetString("text", "Please wait for broadcast to start ..." );
+
+ // send spectators the HLTV director command as a game event
+ m_pHLTVServer->BroadcastEvent( msg );
+ gameeventmanager->FreeEvent( msg );
+ }
+
+ StartBestFixedCameraShot( true );
+}
+
+void CHLTVDirector::StartBestPlayerCameraShot()
+{
+ float flPlayerRanking[MAX_PLAYERS];
+
+ memset( flPlayerRanking, 0, sizeof(flPlayerRanking) );
+
+ int firstIndex = FindFirstEvent( m_nBroadcastTick );
+
+ int index = firstIndex;
+
+ float flBestRank = -1.0f;
+ int iBestCamera = -1;
+ int iBestTarget = -1;
+
+ // sum all ranking values for the cameras
+
+ while( index != m_EventHistory.InvalidIndex() )
+ {
+ CHLTVGameEvent &dc = m_EventHistory[index];
+
+ if ( dc.m_Tick >= m_nNextShotTick )
+ break;
+
+ // search for camera ranking events
+ if ( Q_strcmp( dc.m_Event->GetName(), "hltv_rank_entity") == 0 )
+ {
+ int index = dc.m_Event->GetInt("index");
+
+ if ( index < MAX_PLAYERS )
+ {
+ flPlayerRanking[index] += dc.m_Event->GetFloat("rank" );
+
+ // find best camera
+ if ( flPlayerRanking[index] > flBestRank )
+ {
+ iBestCamera = index;
+ flBestRank = flPlayerRanking[index];
+ iBestTarget = dc.m_Event->GetInt("target");
+ }
+ }
+ }
+
+ index = m_EventHistory.NextInorder( index );
+ }
+
+ if ( iBestCamera != -1 )
+ {
+ // view over shoulder, randomly left or right
+ StartChaseCameraShot( iBestCamera, iBestTarget, 112.0f, 20, (RandomFloat()>0.5)?20:-20, false );
+ }
+ else
+ {
+ StartBestFixedCameraShot( true );
+ }
+}
+
+void CHLTVDirector::StartFixedCameraShot(int iCamera, int iTarget)
+{
+ CBaseEntity *pCamera = m_pFixedCameras[iCamera];
+
+
+ Vector vCamPos = pCamera->GetAbsOrigin();
+ QAngle aViewAngle = pCamera->GetAbsAngles();
+
+ m_iPVSEntity = 0; // don't use camera entity, since it may not been transmitted
+ m_vPVSOrigin = vCamPos;
+
+ IGameEvent *shot = gameeventmanager->CreateEvent( "hltv_fixed", true );
+
+ if ( shot )
+ {
+ shot->SetInt("posx", vCamPos.x );
+ shot->SetInt("posy", vCamPos.y );
+ shot->SetInt("posz", vCamPos.z );
+ shot->SetInt("theta", aViewAngle.x );
+ shot->SetInt("phi", aViewAngle.y );
+ shot->SetInt("target", iTarget );
+ shot->SetFloat("fov", RandomFloat(50,110) );
+
+ // send spectators the HLTV director command as a game event
+ m_pHLTVServer->BroadcastEvent( shot );
+ gameeventmanager->FreeEvent( shot );
+ }
+}
+
+void CHLTVDirector::StartChaseCameraShot(int iTarget1, int iTarget2, int distance, int phi, int theta, bool bInEye)
+{
+ IGameEvent *shot = gameeventmanager->CreateEvent( "hltv_chase", true );
+
+ if ( !shot )
+ return;
+
+ shot->SetInt("target1", iTarget1 );
+ shot->SetInt("target2", iTarget2 );
+ shot->SetInt("distance", distance );
+ shot->SetInt("phi", phi ); // hi/low
+ shot->SetInt( "theta", theta ); // left/right
+ shot->SetInt( "ineye", bInEye?1:0 );
+
+ m_iPVSEntity = iTarget1;
+
+ // send spectators the HLTV director command as a game event
+ m_pHLTVServer->BroadcastEvent( shot );
+ gameeventmanager->FreeEvent( shot );
+}
+
+void CHLTVDirector::StartBestFixedCameraShot( bool bForce )
+{
+ float flCameraRanking[MAX_NUM_CAMERAS];
+
+ if ( m_nNumFixedCameras <= 0 )
+ return;
+
+ memset( flCameraRanking, 0, sizeof(flCameraRanking) );
+
+ int firstIndex = FindFirstEvent( m_nBroadcastTick );
+
+ int index = firstIndex;
+
+ float flBestRank = -1.0f;
+ int iBestCamera = -1;
+ int iBestTarget = -1;
+
+ // sum all ranking values for the cameras
+
+ while( index != m_EventHistory.InvalidIndex() )
+ {
+ CHLTVGameEvent &dc = m_EventHistory[index];
+
+ if ( dc.m_Tick >= m_nNextShotTick )
+ break;
+
+ // search for camera ranking events
+ if ( Q_strcmp( dc.m_Event->GetName(), "hltv_rank_camera") == 0 )
+ {
+ int index = dc.m_Event->GetInt("index");
+ flCameraRanking[index] += dc.m_Event->GetFloat("rank" );
+
+ // find best camera
+ if ( flCameraRanking[index] > flBestRank )
+ {
+ iBestCamera = index;
+ flBestRank = flCameraRanking[index];
+ iBestTarget = dc.m_Event->GetInt("target");
+ }
+ }
+
+ index = m_EventHistory.NextInorder( index );
+ }
+
+ if ( !bForce && flBestRank == 0 )
+ {
+ // if we are not forcing a fixed camera shot, switch to player chase came
+ // if no camera shows any players
+ StartBestPlayerCameraShot();
+ }
+ else if ( iBestCamera != -1 )
+ {
+ StartFixedCameraShot( iBestCamera, iBestTarget );
+ }
+}
+
+void CHLTVDirector::StartRandomShot()
+{
+ int toTick = m_nBroadcastTick + TIME_TO_TICKS ( DEF_SHOT_LENGTH );
+ m_nNextShotTick = MIN( m_nNextShotTick, toTick );
+
+ if ( RandomFloat(0,1) < 0.25 && tv_allow_static_shots.GetBool() )
+ {
+ // create a static shot from a level camera
+ StartBestFixedCameraShot( false );
+ }
+ else
+ {
+ // follow a player
+ StartBestPlayerCameraShot();
+ }
+}
+
+void CHLTVDirector::CreateShotFromEvent( CHLTVGameEvent *event )
+{
+ // show event at least for 2 more seconds after it occured
+ const char *name = event->m_Event->GetName();
+
+ bool bPlayerHurt = Q_strcmp( "player_hurt", name ) == 0;
+ bool bPlayerKilled = Q_strcmp( "player_death", name ) == 0;
+ bool bRoundStart = Q_strcmp( "round_start", name ) == 0;
+ bool bRoundEnd = Q_strcmp( "round_end", name ) == 0;
+
+ if ( bPlayerHurt || bPlayerKilled )
+ {
+ CBaseEntity *victim = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
+ CBaseEntity *attacker = UTIL_PlayerByUserId( event->m_Event->GetInt("attacker") );
+
+ if ( !victim )
+ return;
+
+ if ( attacker == victim || attacker == NULL )
+ {
+ // player killed self or by WORLD
+ StartChaseCameraShot( victim->entindex(), 0, 96, 20, 0, false );
+ }
+ else // attacker != NULL
+ {
+ // check if we would show it from ineye view
+ bool bInEye = (bPlayerKilled && RandomFloat(0,1) > 0.33) || (bPlayerHurt && RandomFloat(0,1) > 0.66);
+
+ // if we show ineye view, show it more likely from killer
+ if ( RandomFloat(0,1) > (bInEye?0.3f:0.7f) )
+ {
+ ::V_swap( attacker, victim );
+ }
+
+ // hurting a victim is shown as chase more often
+ // view from behind over head
+ // lower view point, dramatic
+ // view over shoulder, randomly left or right
+ StartChaseCameraShot( victim->entindex(), attacker->entindex(), 96, -20, (RandomFloat()>0.5)?30:-30, bInEye );
+ }
+
+ // shot 2 seconds after death/hurt
+ m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
+ }
+ else if ( bRoundStart || bRoundEnd )
+ {
+ StartBestFixedCameraShot( false );
+ }
+ else
+ {
+ DevMsg( "No known TV shot for event %s\n", name );
+ }
+}
+
+void CHLTVDirector::CheckHistory()
+{
+ int index = m_EventHistory.FirstInorder();
+ int lastTick = -1;
+
+ while ( index != m_EventHistory.InvalidIndex() )
+ {
+ CHLTVGameEvent &dc = m_EventHistory[index];
+
+ Assert( lastTick <= dc.m_Tick );
+ lastTick = dc.m_Tick;
+
+ index = m_EventHistory.NextInorder( index );
+ }
+}
+
+void CHLTVDirector::RemoveEventsFromHistory(int tick)
+{
+ int index = m_EventHistory.FirstInorder();
+
+ while ( index != m_EventHistory.InvalidIndex() )
+ {
+ CHLTVGameEvent &dc = m_EventHistory[index];
+
+ if ( (dc.m_Tick < tick) || (tick == -1) )
+ {
+ gameeventmanager->FreeEvent( dc.m_Event );
+ dc.m_Event = NULL;
+ m_EventHistory.RemoveAt( index );
+ index = m_EventHistory.FirstInorder(); // start again
+ }
+ else
+ {
+ index = m_EventHistory.NextInorder( index );
+ }
+ }
+
+#ifdef _DEBUG
+ CheckHistory();
+#endif
+}
+
+int CHLTVDirector::FindFirstEvent( int tick )
+{
+ // TODO cache last queried ticks
+
+ int index = m_EventHistory.FirstInorder();
+
+ if ( index == m_EventHistory.InvalidIndex() )
+ return index; // no commands in list
+
+ CHLTVGameEvent *event = &m_EventHistory[index];
+
+ while ( event->m_Tick < tick )
+ {
+ index = m_EventHistory.NextInorder( index );
+
+ if ( index == m_EventHistory.InvalidIndex() )
+ break;
+
+ event = &m_EventHistory[index];
+ }
+
+ return index;
+}
+
+bool CHLTVDirector::SetCameraMan( int iPlayerIndex )
+{
+ if ( !tv_allow_camera_man.GetBool() )
+ return false;
+
+ if ( m_iCameraManIndex == iPlayerIndex )
+ return true;
+
+ // check if somebody else is already the camera man
+ if ( m_iCameraManIndex != 0 && iPlayerIndex != 0 )
+ return false;
+
+ CBasePlayer *pPlayer = NULL;
+
+ if ( iPlayerIndex > 0 )
+ {
+ pPlayer = UTIL_PlayerByIndex( iPlayerIndex );
+ if ( !pPlayer || pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
+ return false;
+ }
+
+ m_iCameraManIndex = iPlayerIndex;
+
+ // create event for director event history
+ IGameEvent *event = gameeventmanager->CreateEvent( "hltv_cameraman" );
+ if ( event )
+ {
+ event->SetInt("index", iPlayerIndex );
+ gameeventmanager->FireEvent( event );
+ }
+
+ CRecipientFilter filter;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+
+ if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_SPECTATOR && !pPlayer->IsFakeClient() )
+ {
+ filter.AddRecipient( pPlayer );
+ }
+ }
+
+ filter.MakeReliable();
+
+ if ( iPlayerIndex > 0 )
+ {
+ // tell all spectators that the camera is in use.
+ char szText[200];
+ Q_snprintf( szText, sizeof(szText), "SourceTV camera is now controlled by %s.", pPlayer->GetPlayerName() );
+ UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, szText );
+ }
+ else
+ {
+ // tell all spectators that the camera is available again.
+ UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "SourceTV camera switched to auto-director mode." );
+ }
+
+ return true;
+}
+
+void CHLTVDirector::FinishCameraManShot()
+{
+ Assert( m_iCameraMan == m_iPVSEntity );
+
+ int index = FindFirstEvent( m_nBroadcastTick );
+
+ if ( index == m_EventHistory.InvalidIndex() )
+ {
+ // check next frame again if event history is empty
+ m_nNextShotTick = m_nBroadcastTick+1;
+ return;
+ }
+
+ m_nNextShotTick = m_nBroadcastTick + TIME_TO_TICKS( MIN_SHOT_LENGTH );
+
+ //check if camera turns camera off within broadcast time and game time
+ while( index != m_EventHistory.InvalidIndex() )
+ {
+ CHLTVGameEvent &dc = m_EventHistory[index];
+
+ if ( dc.m_Tick >= m_nNextShotTick )
+ break;
+
+ if ( Q_strcmp( dc.m_Event->GetName(), "hltv_cameraman") == 0 )
+ {
+ int iNewCameraMan = dc.m_Event->GetInt("index");
+
+ if ( iNewCameraMan == 0 )
+ {
+ // camera man switched camera off
+ m_nNextShotTick = dc.m_Tick+1;
+ m_iCameraMan = 0;
+ return;
+ }
+ }
+
+ index = m_EventHistory.NextInorder( index );
+ }
+
+ // camera man is still recording and live, resend camera man message
+ IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_cameraman", true );
+ if ( msg )
+ {
+ msg->SetInt("index", m_iCameraMan );
+ m_pHLTVServer->BroadcastEvent( msg );
+ gameeventmanager->FreeEvent( msg );
+ }
+
+}
+
+bool CHLTVDirector::StartCameraManShot()
+{
+ Assert( m_nNextShotTick <= m_nBroadcastTick );
+
+ int index = FindFirstEvent( m_nNextShotTick );
+
+ // check for cameraman mode
+ while( index != m_EventHistory.InvalidIndex() )
+ {
+ CHLTVGameEvent &dc = m_EventHistory[index];
+
+ // only check if this is the current tick
+ if ( dc.m_Tick > m_nBroadcastTick )
+ break;
+
+ if ( Q_strcmp( dc.m_Event->GetName(), "hltv_cameraman") == 0 )
+ {
+ if ( dc.m_Event->GetInt("index") > 0 )
+ {
+ // ok, this guy is now the active camera man
+ m_iCameraMan = dc.m_Event->GetInt("index");
+
+ m_iPVSEntity = m_iCameraMan;
+ m_nNextShotTick = m_nBroadcastTick+1; // check setting right on next frame
+
+ // send camera man command to client
+ m_pHLTVServer->BroadcastEvent( dc.m_Event );
+ return true;
+ }
+ }
+
+ index = m_EventHistory.NextInorder( index );
+ }
+
+ return false; // no camera man found
+}
+
+void CHLTVDirector::StartInstantBroadcastShot()
+{
+ m_nNextShotTick = m_nBroadcastTick + TIME_TO_TICKS( MAX_SHOT_LENGTH );
+
+ if ( m_iCameraManIndex > 0 )
+ {
+ // camera man is still recording and live, resend camera man message
+ IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_cameraman", true );
+ if ( msg )
+ {
+ msg->SetInt("index", m_iCameraManIndex );
+ m_pHLTVServer->BroadcastEvent( msg );
+ gameeventmanager->FreeEvent( msg );
+
+ m_iPVSEntity = m_iCameraManIndex;
+ m_nNextShotTick = m_nBroadcastTick+TIME_TO_TICKS( MIN_SHOT_LENGTH );
+ }
+ }
+ else
+ {
+ RemoveEventsFromHistory(-1); // all
+
+ AnalyzePlayers();
+
+ AnalyzeCameras();
+
+ StartRandomShot();
+ }
+}
+
+void CHLTVDirector::StartNewShot()
+{
+ // we can remove all events the
+ int smallestTick = MAX(0, gpGlobals->tickcount - TIME_TO_TICKS(HLTV_MAX_DELAY) );
+ RemoveEventsFromHistory( smallestTick );
+
+ // if the delay time is to short for autodirector, just show next best thing
+ if ( m_fDelay < HLTV_MIN_DIRECTOR_DELAY )
+ {
+ StartInstantBroadcastShot();
+ return;
+ }
+
+ if ( m_iCameraMan > 0 )
+ {
+ // we already have an active camera man,
+ // wait until he releases the "record" lock
+ FinishCameraManShot();
+ return;
+ }
+
+ if ( StartCameraManShot() )
+ {
+ // now we have an active camera man
+ return;
+ }
+
+ // ok, no camera man active, now check how much time
+ // we have for the next shot, if the time diff to the next
+ // important event we have to switch to is too short (<2sec)
+ // just extent the current shot and don't start a new one
+
+ // check the next 8 seconds for interrupts/important events
+ m_nNextShotTick = m_nBroadcastTick + TIME_TO_TICKS( MAX_SHOT_LENGTH );
+
+ if ( m_nBroadcastTick <= 0 )
+ {
+ // game hasn't started yet, we are still in the broadcast delay hole
+ IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_message", true );
+
+ if ( msg )
+ {
+ msg->SetString("text", "Please wait for broadcast to start ..." );
+
+ // send spectators the HLTV director command as a game event
+ m_pHLTVServer->BroadcastEvent( msg );
+ gameeventmanager->FreeEvent( msg );
+ }
+
+ StartBestFixedCameraShot( true );
+ return;
+ }
+
+ int index = FindFirstEvent( m_nBroadcastTick );
+
+ while( index != m_EventHistory.InvalidIndex() )
+ {
+ CHLTVGameEvent &dc = m_EventHistory[index];
+
+ if ( dc.m_Tick >= m_nNextShotTick )
+ break; // we have searched enough
+
+ // a camera man is always interrupting auto director
+ if ( Q_strcmp( dc.m_Event->GetName(), "hltv_cameraman") == 0 )
+ {
+ if ( dc.m_Event->GetInt("index") > 0 )
+ {
+ // stop the next cut when this cameraman starts recording
+ m_nNextShotTick = dc.m_Tick;
+ break;
+ }
+ }
+
+ index = m_EventHistory.NextInorder( index );
+ }
+
+ float flDuration = TICKS_TO_TIME(m_nNextShotTick - m_nBroadcastTick);
+
+ if ( flDuration < MIN_SHOT_LENGTH )
+ return; // not enough time for a new shot
+
+ // find the most interesting game event for next shot
+ CHLTVGameEvent *dc = FindBestGameEvent();
+
+ if ( dc )
+ {
+ // show the game event
+ CreateShotFromEvent( dc );
+ }
+ else
+ {
+ // no interesting events found, start random shot
+ StartRandomShot();
+ }
+}
+
+CHLTVGameEvent *CHLTVDirector::FindBestGameEvent()
+{
+ int bestEvent[4];
+ int bestEventPrio[4];
+
+ Q_memset( bestEvent, 0, sizeof(bestEvent) );
+ Q_memset( bestEventPrio, 0, sizeof(bestEventPrio) );
+
+ int index = FindFirstEvent( m_nBroadcastTick );
+
+ // search for next 4 best events within next 8 seconds
+ for (int i = 0; i<4; i ++)
+ {
+ bestEventPrio[i] = 0;
+ bestEvent[i] = 0;
+
+ int tillTick = m_nBroadcastTick + TIME_TO_TICKS( 2.0f*(1.0f+i) );
+
+ if ( tillTick > m_nNextShotTick )
+ break;
+
+ // sum all action for the next time
+ while ( index != m_EventHistory.InvalidIndex() )
+ {
+ CHLTVGameEvent &event = m_EventHistory[index];
+
+ if ( event.m_Tick > tillTick )
+ break;
+
+ int priority = event.m_Priority;
+
+ if ( priority > bestEventPrio[i] )
+ {
+ bestEvent[i] = index;
+ bestEventPrio[i] = priority;
+ }
+
+ index = m_EventHistory.NextInorder( index );
+ }
+ }
+
+ if ( !( bestEventPrio[0] || bestEventPrio[1] || bestEventPrio[2] ) )
+ return NULL; // no event found at all, give generic algorithm a chance
+
+ // camera cut rules :
+
+ if ( bestEventPrio[1] >= bestEventPrio[0] &&
+ bestEventPrio[1] >= bestEventPrio[2] &&
+ bestEventPrio[1] >= bestEventPrio[3] )
+ {
+ return &m_EventHistory[ bestEvent[1] ]; // best case
+ }
+ else if ( bestEventPrio[0] > bestEventPrio[1] &&
+ bestEventPrio[0] > bestEventPrio[2] )
+ {
+ return &m_EventHistory[ bestEvent[0] ]; // event 0 is very important
+ }
+ else if ( bestEventPrio[2] > bestEventPrio[3] )
+ {
+ return &m_EventHistory[ bestEvent[2] ];
+ }
+ else
+ {
+ // event 4 is the best but too far away, so show event 1
+ if ( bestEvent[0] )
+ return &m_EventHistory[ bestEvent[0] ];
+ else
+ return NULL;
+ }
+}
+
+void CHLTVDirector::AnalyzeCameras()
+{
+ InitRandomOrder( m_nNumFixedCameras );
+
+ for ( int i = 0; i<m_nNumFixedCameras; i++ )
+ {
+ int iCameraIndex = s_RndOrder[i];
+ CBaseEntity *pCamera = m_pFixedCameras[ iCameraIndex ];
+
+ float flRank = 0.0f;
+ int iClosestPlayer = 0;
+ float flClosestPlayerDist = 100000.0f;
+ int nCount = 0; // Number of visible targets
+ Vector vDistribution; vDistribution.Init(); // distribution of targets
+
+ Vector vCamPos = pCamera->GetAbsOrigin();
+
+ for ( int j=0; j<m_nNumActivePlayers; j++ )
+ {
+ CBasePlayer *pPlayer = m_pActivePlayers[j];
+
+ Vector vPlayerPos = pPlayer->GetAbsOrigin();
+
+ float dist = VectorLength( vPlayerPos - vCamPos );
+
+ if ( dist > 1024.0f || dist < 4.0f )
+ continue; // too colse or far away
+
+ // check visibility
+ trace_t tr;
+ UTIL_TraceLine( vCamPos, pPlayer->GetAbsOrigin(), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0 )
+ continue; // not visible for camera
+
+ nCount++;
+
+ // remember closest player
+ if ( dist < flClosestPlayerDist )
+ {
+ iClosestPlayer = pPlayer->entindex();
+ flClosestPlayerDist = dist;
+ }
+
+ Vector v1; AngleVectors( pPlayer->EyeAngles(), &v1 );
+
+ // check players orientation towards camera
+ Vector v2 = vCamPos - vPlayerPos;
+ VectorNormalize( v2 );
+
+ // player/camera cost function:
+ flRank += ( 1.0f/sqrt(dist) ) * WeightedAngle( v1, v2 );
+
+ vDistribution += v2;
+ }
+
+ if ( nCount > 0 )
+ {
+ // normalize distribution
+ flRank *= VectorLength( vDistribution ) / nCount;
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent("hltv_rank_camera");
+
+ if ( event )
+ {
+ event->SetFloat("rank", flRank );
+ event->SetInt("index", iCameraIndex ); // index in m_pFixedCameras
+ event->SetInt("target", iClosestPlayer ); // ent index
+ gameeventmanager->FireEvent( event );
+ }
+ }
+}
+
+void CHLTVDirector::BuildActivePlayerList()
+{
+ // first build list of all active players
+
+ m_nNumActivePlayers = 0;
+
+ for ( int i =1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+
+ if ( !pPlayer )
+ continue;
+
+ if ( !pPlayer->IsAlive() )
+ continue;
+
+ if ( pPlayer->IsObserver() )
+ continue;
+
+ if ( pPlayer->GetTeamNumber() <= TEAM_SPECTATOR )
+ continue;
+
+ m_pActivePlayers[m_nNumActivePlayers] = pPlayer;
+ m_nNumActivePlayers++;
+ }
+}
+
+void CHLTVDirector::AnalyzePlayers()
+{
+ // build list of current active players
+ BuildActivePlayerList();
+
+ // analyzes every active player
+
+ InitRandomOrder( m_nNumActivePlayers );
+
+ for ( int i = 0; i<m_nNumActivePlayers; i++ )
+ {
+ int iPlayerIndex = s_RndOrder[i];
+
+ CBasePlayer *pPlayer = m_pActivePlayers[ iPlayerIndex ];
+
+ float flRank = 0.0f;
+ int iBestFacingPlayer = 0;
+ float flBestFacingPlayer = 0.0f;
+ int nCount = 0; // Number of visible targets
+ Vector vDistribution; vDistribution.Init(); // distribution of targets
+
+ Vector vCamPos = pPlayer->GetAbsOrigin();
+
+ Vector v1; AngleVectors( pPlayer->EyeAngles(), &v1 );
+
+ v1 *= -1; // inverted
+
+ for ( int j=0; j<m_nNumActivePlayers; j++ )
+ {
+ if ( iPlayerIndex == j )
+ continue; // don't check against itself
+
+ CBasePlayer *pOtherPlayer = m_pActivePlayers[j];
+
+ Vector vPlayerPos = pOtherPlayer->GetAbsOrigin();
+
+ float dist = VectorLength( vPlayerPos - vCamPos );
+
+ if ( dist > 1024.0f || dist < 4.0f )
+ continue; // too close or far away
+
+ // check visibility
+ trace_t tr;
+ UTIL_TraceLine( vCamPos, pOtherPlayer->GetAbsOrigin(), MASK_SOLID, pOtherPlayer, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0 )
+ continue; // not visible for camera
+
+ nCount++;
+
+ // check players orientation towards camera
+ Vector v2; AngleVectors( pOtherPlayer->EyeAngles(), &v2 );
+
+ float facing = WeightedAngle( v1, v2 );
+
+ // remember closest player
+ if ( facing > flBestFacingPlayer )
+ {
+ iBestFacingPlayer = pOtherPlayer->entindex();
+ flBestFacingPlayer = facing;
+ }
+
+ // player/camera cost function:
+ flRank += ( 1.0f/sqrt(dist) ) * facing;
+
+ vDistribution += v2;
+ }
+
+ if ( nCount > 0 )
+ {
+ float flDistribution = VectorLength( vDistribution ) / nCount; // normalize distribution
+ flRank *= flDistribution;
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent("hltv_rank_entity");
+ if ( event )
+ {
+ event->SetInt("index", pPlayer->entindex() );
+ event->SetFloat("rank", flRank );
+ event->SetInt("target", iBestFacingPlayer ); // ent index
+ gameeventmanager->FireEvent( event );
+ }
+ }
+}
+