diff options
Diffstat (limited to 'game/server/hltvdirector.cpp')
| -rw-r--r-- | game/server/hltvdirector.cpp | 1184 |
1 files changed, 1184 insertions, 0 deletions
diff --git a/game/server/hltvdirector.cpp b/game/server/hltvdirector.cpp new file mode 100644 index 0000000..34d6973 --- /dev/null +++ b/game/server/hltvdirector.cpp @@ -0,0 +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 ); + } + } +} + |