diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/hltvdirector.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-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.cpp | 2368 |
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 ); + } + } +} + |