Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1184 lines
28 KiB

//========= 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 );
}
}
}