|
|
//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// replaydirector.cpp: implementation of the CReplayDirector class.
//
//////////////////////////////////////////////////////////////////////
#include "cbase.h"
#if defined( REPLAY_ENABLED )
#include "replaydirector.h"
#include "keyvalues.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//////////////////////////////////////////////////////////////////////
// Expose interface
//////////////////////////////////////////////////////////////////////
static CReplayDirector s_ReplayDirector; // singleton
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CReplayDirector, IReplayDirector, INTERFACEVERSION_REPLAYDIRECTOR, s_ReplayDirector );
CReplayDirector* ReplayDirector() { return &s_ReplayDirector; }
#if defined( REPLAY_ENABLED )
IGameSystem* ReplayDirectorSystem() { return &s_ReplayDirector; } #endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
static ConVar replay_delay( "replay_delay", "30", 0, "Replay broadcast delay in seconds", true, 0, true, REPLAY_MAX_DELAY ); static ConVar replay_allow_static_shots( "replay_allow_static_shots", "1", 0, "Auto director uses fixed level cameras for shots" ); static ConVar replay_allow_camera_man( "replay_allow_camera_man", "1", 0, "Auto director allows spectators to become camera man" );
static bool GameEventLessFunc( CReplayGameEvent const &e1, CReplayGameEvent 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( TF_DLL )// add your mod here if you use your own director
static CReplayDirector s_ReplayDirector; // singleton
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CReplayDirector, IReplayDirector, INTERFACEVERSION_REPLAYDIRECTOR, s_ReplayDirector );
CReplayDirector* ReplayDirector() { return &s_ReplayDirector; }
#if defined( REPLAY_ENABLED )
IGameSystem* ReplayDirectorSystem() { return &s_ReplayDirector; } #endif
#endif // MODs
CReplayDirector::CReplayDirector() { m_iPVSEntity = 0; m_fDelay = 30.0; m_iLastPlayer = 1; m_pReplayServer = NULL; m_pReplayClient = NULL; m_iCameraMan = 0; m_nNumFixedCameras = 0; m_EventHistory.SetLessFunc( GameEventLessFunc ); m_nNextAnalyzeTick = 0; m_iCameraManIndex = 0; }
CReplayDirector::~CReplayDirector() {
}
bool CReplayDirector::Init() { return gameeventmanager->LoadEventsFromFile( "resource/replayevents.res" ) > 0; }
void CReplayDirector::Shutdown() { RemoveEventsFromHistory(-1); // all
}
void CReplayDirector::FireGameEvent( IGameEvent * event ) { if ( !m_pReplayServer ) return; // don't do anything
CReplayGameEvent 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 ); }
IReplayServer* CReplayDirector::GetReplayServer( void ) { return m_pReplayServer; }
void CReplayDirector::SetReplayServer( IReplayServer *replay ) { RemoveEventsFromHistory(-1); // all
if ( replay ) { m_pReplayClient = UTIL_PlayerByIndex( replay->GetReplaySlot() + 1 );
if ( m_pReplayClient && m_pReplayClient->IsReplay() ) { m_pReplayServer = replay; } else { m_pReplayServer = NULL; Error( "Couldn't find Replay client player." ); }
// register for events the director needs to know
ListenForGameEvent( "player_hurt" ); ListenForGameEvent( "player_death" ); ListenForGameEvent( "round_end" ); ListenForGameEvent( "round_start" ); ListenForGameEvent( "replay_cameraman" ); ListenForGameEvent( "replay_rank_entity" ); ListenForGameEvent( "replay_rank_camera" ); } else { // deactivate Replay director
m_pReplayServer = NULL; } }
bool CReplayDirector::IsActive( void ) { return (m_pReplayServer != NULL ); }
float CReplayDirector::GetDelay( void ) { return m_fDelay; }
int CReplayDirector::GetDirectorTick( void ) { // just simple delay it
return m_nBroadcastTick; }
int CReplayDirector::GetPVSEntity( void ) { return m_iPVSEntity; }
Vector CReplayDirector::GetPVSOrigin( void ) { return m_vPVSOrigin; }
void CReplayDirector::UpdateSettings() { // set delay
m_fDelay = replay_delay.GetFloat();
int newBroadcastTick = gpGlobals->tickcount; if ( m_fDelay < REPLAY_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** CReplayDirector::GetModEvents() { static const char *s_modevents[] = { "replay_status", "replay_chat", "player_connect", "player_disconnect", "player_team", "player_info", "server_cvar", "player_death", "player_chat", "round_start", "round_end", NULL };
return s_modevents; }
void CReplayDirector::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 CReplayDirector::LevelInitPostEntity( void ) { BuildCameraList();
m_vPVSOrigin.Init(); m_iPVSEntity = 0; m_nNextShotTick = 0; m_nNextAnalyzeTick = 0; m_iCameraManIndex = 0;
RemoveEventsFromHistory(-1); // all
// DevMsg("Replay Director: found %i fixed cameras.\n", m_nNumFixedCameras );
}
void CReplayDirector::FrameUpdatePostEntityThink( void ) { if ( !m_pReplayServer ) return; // don't do anything
// This function is called each tick
UpdateSettings(); // update settings from cvars
if ( (m_nNextAnalyzeTick < gpGlobals->tickcount) && (m_fDelay >= REPLAY_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 CReplayDirector::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( "replay_message", true );
if ( msg ) { msg->SetString("text", "Please wait for broadcast to start ..." );
// send spectators the Replay director command as a game event
m_pReplayServer->BroadcastEvent( msg ); gameeventmanager->FreeEvent( msg ); }
StartBestFixedCameraShot( true ); }
void CReplayDirector::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() ) { CReplayGameEvent &dc = m_EventHistory[index];
if ( dc.m_Tick >= m_nNextShotTick ) break;
// search for camera ranking events
if ( Q_strcmp( dc.m_Event->GetName(), "replay_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 CReplayDirector::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( "replay_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 Replay director command as a game event
m_pReplayServer->BroadcastEvent( shot ); gameeventmanager->FreeEvent( shot ); } }
void CReplayDirector::StartChaseCameraShot(int iTarget1, int iTarget2, int distance, int phi, int theta, bool bInEye) { IGameEvent *shot = gameeventmanager->CreateEvent( "replay_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 Replay director command as a game event
m_pReplayServer->BroadcastEvent( shot ); gameeventmanager->FreeEvent( shot ); }
void CReplayDirector::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() ) { CReplayGameEvent &dc = m_EventHistory[index];
if ( dc.m_Tick >= m_nNextShotTick ) break;
// search for camera ranking events
if ( Q_strcmp( dc.m_Event->GetName(), "replay_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 CReplayDirector::StartRandomShot() { int toTick = m_nBroadcastTick + TIME_TO_TICKS ( DEF_SHOT_LENGTH ); m_nNextShotTick = MIN( m_nNextShotTick, toTick );
if ( RandomFloat(0,1) < 0.25 && replay_allow_static_shots.GetBool() ) { // create a static shot from a level camera
StartBestFixedCameraShot( false ); } else { // follow a player
StartBestPlayerCameraShot(); } }
void CReplayDirector::CreateShotFromEvent( CReplayGameEvent *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) ) { 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 CReplayDirector::CheckHistory() { int index = m_EventHistory.FirstInorder(); int lastTick = -1;
while ( index != m_EventHistory.InvalidIndex() ) { CReplayGameEvent &dc = m_EventHistory[index];
Assert( lastTick <= dc.m_Tick ); lastTick = dc.m_Tick;
index = m_EventHistory.NextInorder( index ); } }
void CReplayDirector::RemoveEventsFromHistory(int tick) { int index = m_EventHistory.FirstInorder();
while ( index != m_EventHistory.InvalidIndex() ) { CReplayGameEvent &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 CReplayDirector::FindFirstEvent( int tick ) { // TODO cache last queried ticks
int index = m_EventHistory.FirstInorder();
if ( index == m_EventHistory.InvalidIndex() ) return index; // no commands in list
CReplayGameEvent *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 CReplayDirector::SetCameraMan( int iPlayerIndex ) { if ( !replay_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( "replay_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), "Replay 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, "Replay camera switched to auto-director mode." ); } return true; }
void CReplayDirector::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() ) { CReplayGameEvent &dc = m_EventHistory[index];
if ( dc.m_Tick >= m_nNextShotTick ) break;
if ( Q_strcmp( dc.m_Event->GetName(), "replay_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( "replay_cameraman", true ); if ( msg ) { msg->SetInt("index", m_iCameraMan ); m_pReplayServer->BroadcastEvent( msg ); gameeventmanager->FreeEvent( msg ); }
}
bool CReplayDirector::StartCameraManShot() { Assert( m_nNextShotTick <= m_nBroadcastTick );
int index = FindFirstEvent( m_nNextShotTick );
// check for cameraman mode
while( index != m_EventHistory.InvalidIndex() ) { CReplayGameEvent &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(), "replay_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_pReplayServer->BroadcastEvent( dc.m_Event ); return true; } }
index = m_EventHistory.NextInorder( index ); }
return false; // no camera man found
}
void CReplayDirector::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( "replay_cameraman", true ); if ( msg ) { msg->SetInt("index", m_iCameraManIndex ); m_pReplayServer->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 CReplayDirector::StartNewShot() { // we can remove all events the
int smallestTick = MAX(0, gpGlobals->tickcount - TIME_TO_TICKS(REPLAY_MAX_DELAY) ); RemoveEventsFromHistory( smallestTick );
// if the delay time is to short for autodirector, just show next best thing
if ( m_fDelay < REPLAY_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( "replay_message", true );
if ( msg ) { msg->SetString("text", "Please wait for broadcast to start ..." ); // send spectators the Replay director command as a game event
m_pReplayServer->BroadcastEvent( msg ); gameeventmanager->FreeEvent( msg ); }
StartBestFixedCameraShot( true ); return; } int index = FindFirstEvent( m_nBroadcastTick );
while( index != m_EventHistory.InvalidIndex() ) { CReplayGameEvent &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(), "replay_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
CReplayGameEvent *dc = FindBestGameEvent();
if ( dc ) { // show the game event
CreateShotFromEvent( dc ); } else { // no interesting events found, start random shot
StartRandomShot(); } }
CReplayGameEvent *CReplayDirector::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() ) { CReplayGameEvent &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 CReplayDirector::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("replay_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 CReplayDirector::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 CReplayDirector::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("replay_rank_entity"); if ( event ) { event->SetInt("index", pPlayer->entindex() ); event->SetFloat("rank", flRank ); event->SetInt("target", iBestFacingPlayer ); // ent index
gameeventmanager->FireEvent( event ); } } }
#endif
|