Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // hltvdirector.cpp: implementation of the CHLTVDirector class.
  9. //
  10. //////////////////////////////////////////////////////////////////////
  11. #include "cbase.h"
  12. #include "hltvdirector.h"
  13. #include "KeyValues.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. //////////////////////////////////////////////////////////////////////
  17. // Construction/Destruction
  18. //////////////////////////////////////////////////////////////////////
  19. static ConVar tv_delay( "tv_delay", "30", 0, "SourceTV broadcast delay in seconds", true, 0, true, HLTV_MAX_DELAY );
  20. static ConVar tv_allow_static_shots( "tv_allow_static_shots", "1", 0, "Auto director uses fixed level cameras for shots" );
  21. static ConVar tv_allow_camera_man( "tv_allow_camera_man", "1", 0, "Auto director allows spectators to become camera man" );
  22. static bool GameEventLessFunc( CHLTVGameEvent const &e1, CHLTVGameEvent const &e2 )
  23. {
  24. return e1.m_Tick < e2.m_Tick;
  25. }
  26. #define RANDOM_MAX_ELEMENTS 256
  27. static int s_RndOrder[RANDOM_MAX_ELEMENTS];
  28. static void InitRandomOrder(int nFields)
  29. {
  30. if ( nFields > RANDOM_MAX_ELEMENTS )
  31. {
  32. Assert( nFields > RANDOM_MAX_ELEMENTS );
  33. nFields = RANDOM_MAX_ELEMENTS;
  34. }
  35. for ( int i=0; i<nFields; i++ )
  36. {
  37. s_RndOrder[i]=i;
  38. }
  39. for ( int i=0; i<(nFields/2); i++ )
  40. {
  41. int pos1 = RandomInt( 0, nFields-1 );
  42. int pos2 = RandomInt( 0, nFields-1 );
  43. int temp = s_RndOrder[pos1];
  44. s_RndOrder[pos1] = s_RndOrder[pos2];
  45. s_RndOrder[pos2] = temp;
  46. }
  47. };
  48. static float WeightedAngle( Vector vec1, Vector vec2)
  49. {
  50. VectorNormalize( vec1 );
  51. VectorNormalize( vec2 );
  52. float a = DotProduct( vec1, vec2 ); // a = [-1,1]
  53. a = (a + 1.0f) / 2.0f;
  54. Assert ( a <= 1 && a >= 0 );
  55. return a*a; // vectors are facing opposite direction
  56. }
  57. #if !defined( CSTRIKE_DLL ) && !defined( DOD_DLL ) && !defined( TF_DLL )// add your mod here if you use your own director
  58. static CHLTVDirector s_HLTVDirector; // singleton
  59. EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CHLTVDirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR, s_HLTVDirector );
  60. CHLTVDirector* HLTVDirector()
  61. {
  62. return &s_HLTVDirector;
  63. }
  64. IGameSystem* HLTVDirectorSystem()
  65. {
  66. return &s_HLTVDirector;
  67. }
  68. #endif // MODs
  69. CHLTVDirector::CHLTVDirector()
  70. {
  71. m_iPVSEntity = 0;
  72. m_fDelay = 30.0;
  73. m_iLastPlayer = 1;
  74. m_pHLTVServer = NULL;
  75. m_pHLTVClient = NULL;
  76. m_iCameraMan = 0;
  77. m_nNumFixedCameras = 0;
  78. m_EventHistory.SetLessFunc( GameEventLessFunc );
  79. m_nNextAnalyzeTick = 0;
  80. m_iCameraManIndex = 0;
  81. }
  82. CHLTVDirector::~CHLTVDirector()
  83. {
  84. }
  85. bool CHLTVDirector::Init()
  86. {
  87. return gameeventmanager->LoadEventsFromFile( "resource/hltvevents.res" ) > 0;
  88. }
  89. void CHLTVDirector::Shutdown()
  90. {
  91. RemoveEventsFromHistory(-1); // all
  92. }
  93. void CHLTVDirector::FireGameEvent( IGameEvent * event )
  94. {
  95. if ( !m_pHLTVServer )
  96. return; // don't do anything
  97. CHLTVGameEvent gameevent;
  98. gameevent.m_Event = gameeventmanager->DuplicateEvent( event );
  99. gameevent.m_Priority = event->GetInt( "priority", -1 ); // priorities are leveled between 0..10, -1 means ignore
  100. gameevent.m_Tick = gpGlobals->tickcount;
  101. m_EventHistory.Insert( gameevent );
  102. }
  103. IHLTVServer* CHLTVDirector::GetHLTVServer( void )
  104. {
  105. return m_pHLTVServer;
  106. }
  107. void CHLTVDirector::SetHLTVServer( IHLTVServer *hltv )
  108. {
  109. RemoveEventsFromHistory(-1); // all
  110. if ( hltv )
  111. {
  112. m_pHLTVClient = UTIL_PlayerByIndex( hltv->GetHLTVSlot() + 1 );
  113. if ( m_pHLTVClient && m_pHLTVClient->IsHLTV() )
  114. {
  115. m_pHLTVServer = hltv;
  116. }
  117. else
  118. {
  119. m_pHLTVServer = NULL;
  120. Error( "Couldn't find HLTV client player." );
  121. }
  122. // register for events the director needs to know
  123. ListenForGameEvent( "player_hurt" );
  124. ListenForGameEvent( "player_death" );
  125. ListenForGameEvent( "round_end" );
  126. ListenForGameEvent( "round_start" );
  127. ListenForGameEvent( "hltv_cameraman" );
  128. ListenForGameEvent( "hltv_rank_entity" );
  129. ListenForGameEvent( "hltv_rank_camera" );
  130. }
  131. else
  132. {
  133. // deactivate HLTV director
  134. m_pHLTVServer = NULL;
  135. }
  136. }
  137. bool CHLTVDirector::IsActive( void )
  138. {
  139. return (m_pHLTVServer != NULL );
  140. }
  141. float CHLTVDirector::GetDelay( void )
  142. {
  143. return m_fDelay;
  144. }
  145. int CHLTVDirector::GetDirectorTick( void )
  146. {
  147. // just simple delay it
  148. return m_nBroadcastTick;
  149. }
  150. int CHLTVDirector::GetPVSEntity( void )
  151. {
  152. return m_iPVSEntity;
  153. }
  154. Vector CHLTVDirector::GetPVSOrigin( void )
  155. {
  156. return m_vPVSOrigin;
  157. }
  158. void CHLTVDirector::UpdateSettings()
  159. {
  160. // set delay
  161. m_fDelay = tv_delay.GetFloat();
  162. int newBroadcastTick = gpGlobals->tickcount;
  163. if ( m_fDelay < HLTV_MIN_DIRECTOR_DELAY )
  164. {
  165. // instant broadcast, no delay
  166. m_fDelay = 0.0;
  167. }
  168. else
  169. {
  170. // broadcast time is current time - delay time
  171. newBroadcastTick -= TIME_TO_TICKS( m_fDelay );
  172. }
  173. if( (m_nBroadcastTick == 0) && (newBroadcastTick > 0) )
  174. {
  175. // we start broadcasting right now, reset NextShotTimer
  176. m_nNextShotTick = 0;
  177. }
  178. // check if camera man is still valid
  179. if ( m_iCameraManIndex > 0 )
  180. {
  181. CBasePlayer *pPlayer = UTIL_PlayerByIndex( m_iCameraManIndex );
  182. if ( !pPlayer || pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
  183. {
  184. SetCameraMan( 0 );
  185. }
  186. }
  187. m_nBroadcastTick = MAX( 0, newBroadcastTick );
  188. }
  189. const char** CHLTVDirector::GetModEvents()
  190. {
  191. static const char *s_modevents[] =
  192. {
  193. "hltv_status",
  194. "hltv_chat",
  195. "player_connect",
  196. "player_disconnect",
  197. "player_team",
  198. "player_info",
  199. "server_cvar",
  200. "player_death",
  201. "player_chat",
  202. "round_start",
  203. "round_end",
  204. NULL
  205. };
  206. return s_modevents;
  207. }
  208. void CHLTVDirector::BuildCameraList( void )
  209. {
  210. m_nNumFixedCameras = 0;
  211. memset( m_pFixedCameras, 0, sizeof ( m_pFixedCameras ) );
  212. CBaseEntity *pCamera = gEntList.FindEntityByClassname( NULL, GetFixedCameraEntityName() );
  213. while ( pCamera && m_nNumFixedCameras < MAX_NUM_CAMERAS)
  214. {
  215. CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, STRING(pCamera->m_target) );
  216. if ( pTarget )
  217. {
  218. // look at target if any given
  219. QAngle angles;
  220. VectorAngles( pTarget->GetAbsOrigin() - pCamera->GetAbsOrigin(), angles );
  221. pCamera->SetAbsAngles( angles );
  222. }
  223. m_pFixedCameras[m_nNumFixedCameras] = pCamera;
  224. m_nNumFixedCameras++;
  225. pCamera = gEntList.FindEntityByClassname( pCamera, GetFixedCameraEntityName() );
  226. }
  227. }
  228. // this is called with every new map
  229. void CHLTVDirector::LevelInitPostEntity( void )
  230. {
  231. BuildCameraList();
  232. m_vPVSOrigin.Init();
  233. m_iPVSEntity = 0;
  234. m_nNextShotTick = 0;
  235. m_nNextAnalyzeTick = 0;
  236. m_iCameraManIndex = 0;
  237. RemoveEventsFromHistory(-1); // all
  238. // DevMsg("HLTV Director: found %i fixed cameras.\n", m_nNumFixedCameras );
  239. }
  240. void CHLTVDirector::FrameUpdatePostEntityThink( void )
  241. {
  242. if ( !m_pHLTVServer )
  243. return; // don't do anything
  244. // This function is called each tick
  245. UpdateSettings(); // update settings from cvars
  246. if ( (m_nNextAnalyzeTick < gpGlobals->tickcount) &&
  247. (m_fDelay >= HLTV_MIN_DIRECTOR_DELAY) )
  248. {
  249. m_nNextAnalyzeTick = gpGlobals->tickcount + TIME_TO_TICKS( 0.5f );
  250. AnalyzePlayers();
  251. AnalyzeCameras();
  252. }
  253. if ( m_nBroadcastTick <= 0 )
  254. {
  255. // game start is still in delay loop
  256. StartDelayMessage();
  257. }
  258. else if ( m_nNextShotTick <= m_nBroadcastTick )
  259. {
  260. // game is being broadcasted, generate camera shots
  261. StartNewShot();
  262. }
  263. }
  264. void CHLTVDirector::StartDelayMessage()
  265. {
  266. if ( m_nNextShotTick > gpGlobals->tickcount )
  267. return;
  268. // check the next 8 seconds for interrupts/important events
  269. m_nNextShotTick = gpGlobals->tickcount + TIME_TO_TICKS( DEF_SHOT_LENGTH );
  270. // game hasn't started yet, we are still in the broadcast delay hole
  271. IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_message", true );
  272. if ( msg )
  273. {
  274. msg->SetString("text", "Please wait for broadcast to start ..." );
  275. // send spectators the HLTV director command as a game event
  276. m_pHLTVServer->BroadcastEvent( msg );
  277. gameeventmanager->FreeEvent( msg );
  278. }
  279. StartBestFixedCameraShot( true );
  280. }
  281. void CHLTVDirector::StartBestPlayerCameraShot()
  282. {
  283. float flPlayerRanking[MAX_PLAYERS];
  284. memset( flPlayerRanking, 0, sizeof(flPlayerRanking) );
  285. int firstIndex = FindFirstEvent( m_nBroadcastTick );
  286. int index = firstIndex;
  287. float flBestRank = -1.0f;
  288. int iBestCamera = -1;
  289. int iBestTarget = -1;
  290. // sum all ranking values for the cameras
  291. while( index != m_EventHistory.InvalidIndex() )
  292. {
  293. CHLTVGameEvent &dc = m_EventHistory[index];
  294. if ( dc.m_Tick >= m_nNextShotTick )
  295. break;
  296. // search for camera ranking events
  297. if ( Q_strcmp( dc.m_Event->GetName(), "hltv_rank_entity") == 0 )
  298. {
  299. int index = dc.m_Event->GetInt("index");
  300. if ( index < MAX_PLAYERS )
  301. {
  302. flPlayerRanking[index] += dc.m_Event->GetFloat("rank" );
  303. // find best camera
  304. if ( flPlayerRanking[index] > flBestRank )
  305. {
  306. iBestCamera = index;
  307. flBestRank = flPlayerRanking[index];
  308. iBestTarget = dc.m_Event->GetInt("target");
  309. }
  310. }
  311. }
  312. index = m_EventHistory.NextInorder( index );
  313. }
  314. if ( iBestCamera != -1 )
  315. {
  316. // view over shoulder, randomly left or right
  317. StartChaseCameraShot( iBestCamera, iBestTarget, 112.0f, 20, (RandomFloat()>0.5)?20:-20, false );
  318. }
  319. else
  320. {
  321. StartBestFixedCameraShot( true );
  322. }
  323. }
  324. void CHLTVDirector::StartFixedCameraShot(int iCamera, int iTarget)
  325. {
  326. CBaseEntity *pCamera = m_pFixedCameras[iCamera];
  327. Vector vCamPos = pCamera->GetAbsOrigin();
  328. QAngle aViewAngle = pCamera->GetAbsAngles();
  329. m_iPVSEntity = 0; // don't use camera entity, since it may not been transmitted
  330. m_vPVSOrigin = vCamPos;
  331. IGameEvent *shot = gameeventmanager->CreateEvent( "hltv_fixed", true );
  332. if ( shot )
  333. {
  334. shot->SetInt("posx", vCamPos.x );
  335. shot->SetInt("posy", vCamPos.y );
  336. shot->SetInt("posz", vCamPos.z );
  337. shot->SetInt("theta", aViewAngle.x );
  338. shot->SetInt("phi", aViewAngle.y );
  339. shot->SetInt("target", iTarget );
  340. shot->SetFloat("fov", RandomFloat(50,110) );
  341. // send spectators the HLTV director command as a game event
  342. m_pHLTVServer->BroadcastEvent( shot );
  343. gameeventmanager->FreeEvent( shot );
  344. }
  345. }
  346. void CHLTVDirector::StartChaseCameraShot(int iTarget1, int iTarget2, int distance, int phi, int theta, bool bInEye)
  347. {
  348. IGameEvent *shot = gameeventmanager->CreateEvent( "hltv_chase", true );
  349. if ( !shot )
  350. return;
  351. shot->SetInt("target1", iTarget1 );
  352. shot->SetInt("target2", iTarget2 );
  353. shot->SetInt("distance", distance );
  354. shot->SetInt("phi", phi ); // hi/low
  355. shot->SetInt( "theta", theta ); // left/right
  356. shot->SetInt( "ineye", bInEye?1:0 );
  357. m_iPVSEntity = iTarget1;
  358. // send spectators the HLTV director command as a game event
  359. m_pHLTVServer->BroadcastEvent( shot );
  360. gameeventmanager->FreeEvent( shot );
  361. }
  362. void CHLTVDirector::StartBestFixedCameraShot( bool bForce )
  363. {
  364. float flCameraRanking[MAX_NUM_CAMERAS];
  365. if ( m_nNumFixedCameras <= 0 )
  366. return;
  367. memset( flCameraRanking, 0, sizeof(flCameraRanking) );
  368. int firstIndex = FindFirstEvent( m_nBroadcastTick );
  369. int index = firstIndex;
  370. float flBestRank = -1.0f;
  371. int iBestCamera = -1;
  372. int iBestTarget = -1;
  373. // sum all ranking values for the cameras
  374. while( index != m_EventHistory.InvalidIndex() )
  375. {
  376. CHLTVGameEvent &dc = m_EventHistory[index];
  377. if ( dc.m_Tick >= m_nNextShotTick )
  378. break;
  379. // search for camera ranking events
  380. if ( Q_strcmp( dc.m_Event->GetName(), "hltv_rank_camera") == 0 )
  381. {
  382. int index = dc.m_Event->GetInt("index");
  383. flCameraRanking[index] += dc.m_Event->GetFloat("rank" );
  384. // find best camera
  385. if ( flCameraRanking[index] > flBestRank )
  386. {
  387. iBestCamera = index;
  388. flBestRank = flCameraRanking[index];
  389. iBestTarget = dc.m_Event->GetInt("target");
  390. }
  391. }
  392. index = m_EventHistory.NextInorder( index );
  393. }
  394. if ( !bForce && flBestRank == 0 )
  395. {
  396. // if we are not forcing a fixed camera shot, switch to player chase came
  397. // if no camera shows any players
  398. StartBestPlayerCameraShot();
  399. }
  400. else if ( iBestCamera != -1 )
  401. {
  402. StartFixedCameraShot( iBestCamera, iBestTarget );
  403. }
  404. }
  405. void CHLTVDirector::StartRandomShot()
  406. {
  407. int toTick = m_nBroadcastTick + TIME_TO_TICKS ( DEF_SHOT_LENGTH );
  408. m_nNextShotTick = MIN( m_nNextShotTick, toTick );
  409. if ( RandomFloat(0,1) < 0.25 && tv_allow_static_shots.GetBool() )
  410. {
  411. // create a static shot from a level camera
  412. StartBestFixedCameraShot( false );
  413. }
  414. else
  415. {
  416. // follow a player
  417. StartBestPlayerCameraShot();
  418. }
  419. }
  420. void CHLTVDirector::CreateShotFromEvent( CHLTVGameEvent *event )
  421. {
  422. // show event at least for 2 more seconds after it occured
  423. const char *name = event->m_Event->GetName();
  424. bool bPlayerHurt = Q_strcmp( "player_hurt", name ) == 0;
  425. bool bPlayerKilled = Q_strcmp( "player_death", name ) == 0;
  426. bool bRoundStart = Q_strcmp( "round_start", name ) == 0;
  427. bool bRoundEnd = Q_strcmp( "round_end", name ) == 0;
  428. if ( bPlayerHurt || bPlayerKilled )
  429. {
  430. CBaseEntity *victim = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
  431. CBaseEntity *attacker = UTIL_PlayerByUserId( event->m_Event->GetInt("attacker") );
  432. if ( !victim )
  433. return;
  434. if ( attacker == victim || attacker == NULL )
  435. {
  436. // player killed self or by WORLD
  437. StartChaseCameraShot( victim->entindex(), 0, 96, 20, 0, false );
  438. }
  439. else // attacker != NULL
  440. {
  441. // check if we would show it from ineye view
  442. bool bInEye = (bPlayerKilled && RandomFloat(0,1) > 0.33) || (bPlayerHurt && RandomFloat(0,1) > 0.66);
  443. // if we show ineye view, show it more likely from killer
  444. if ( RandomFloat(0,1) > (bInEye?0.3f:0.7f) )
  445. {
  446. ::V_swap( attacker, victim );
  447. }
  448. // hurting a victim is shown as chase more often
  449. // view from behind over head
  450. // lower view point, dramatic
  451. // view over shoulder, randomly left or right
  452. StartChaseCameraShot( victim->entindex(), attacker->entindex(), 96, -20, (RandomFloat()>0.5)?30:-30, bInEye );
  453. }
  454. // shot 2 seconds after death/hurt
  455. m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
  456. }
  457. else if ( bRoundStart || bRoundEnd )
  458. {
  459. StartBestFixedCameraShot( false );
  460. }
  461. else
  462. {
  463. DevMsg( "No known TV shot for event %s\n", name );
  464. }
  465. }
  466. void CHLTVDirector::CheckHistory()
  467. {
  468. int index = m_EventHistory.FirstInorder();
  469. int lastTick = -1;
  470. while ( index != m_EventHistory.InvalidIndex() )
  471. {
  472. CHLTVGameEvent &dc = m_EventHistory[index];
  473. Assert( lastTick <= dc.m_Tick );
  474. lastTick = dc.m_Tick;
  475. index = m_EventHistory.NextInorder( index );
  476. }
  477. }
  478. void CHLTVDirector::RemoveEventsFromHistory(int tick)
  479. {
  480. int index = m_EventHistory.FirstInorder();
  481. while ( index != m_EventHistory.InvalidIndex() )
  482. {
  483. CHLTVGameEvent &dc = m_EventHistory[index];
  484. if ( (dc.m_Tick < tick) || (tick == -1) )
  485. {
  486. gameeventmanager->FreeEvent( dc.m_Event );
  487. dc.m_Event = NULL;
  488. m_EventHistory.RemoveAt( index );
  489. index = m_EventHistory.FirstInorder(); // start again
  490. }
  491. else
  492. {
  493. index = m_EventHistory.NextInorder( index );
  494. }
  495. }
  496. #ifdef _DEBUG
  497. CheckHistory();
  498. #endif
  499. }
  500. int CHLTVDirector::FindFirstEvent( int tick )
  501. {
  502. // TODO cache last queried ticks
  503. int index = m_EventHistory.FirstInorder();
  504. if ( index == m_EventHistory.InvalidIndex() )
  505. return index; // no commands in list
  506. CHLTVGameEvent *event = &m_EventHistory[index];
  507. while ( event->m_Tick < tick )
  508. {
  509. index = m_EventHistory.NextInorder( index );
  510. if ( index == m_EventHistory.InvalidIndex() )
  511. break;
  512. event = &m_EventHistory[index];
  513. }
  514. return index;
  515. }
  516. bool CHLTVDirector::SetCameraMan( int iPlayerIndex )
  517. {
  518. if ( !tv_allow_camera_man.GetBool() )
  519. return false;
  520. if ( m_iCameraManIndex == iPlayerIndex )
  521. return true;
  522. // check if somebody else is already the camera man
  523. if ( m_iCameraManIndex != 0 && iPlayerIndex != 0 )
  524. return false;
  525. CBasePlayer *pPlayer = NULL;
  526. if ( iPlayerIndex > 0 )
  527. {
  528. pPlayer = UTIL_PlayerByIndex( iPlayerIndex );
  529. if ( !pPlayer || pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
  530. return false;
  531. }
  532. m_iCameraManIndex = iPlayerIndex;
  533. // create event for director event history
  534. IGameEvent *event = gameeventmanager->CreateEvent( "hltv_cameraman" );
  535. if ( event )
  536. {
  537. event->SetInt("index", iPlayerIndex );
  538. gameeventmanager->FireEvent( event );
  539. }
  540. CRecipientFilter filter;
  541. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  542. {
  543. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  544. if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_SPECTATOR && !pPlayer->IsFakeClient() )
  545. {
  546. filter.AddRecipient( pPlayer );
  547. }
  548. }
  549. filter.MakeReliable();
  550. if ( iPlayerIndex > 0 )
  551. {
  552. // tell all spectators that the camera is in use.
  553. char szText[200];
  554. Q_snprintf( szText, sizeof(szText), "SourceTV camera is now controlled by %s.", pPlayer->GetPlayerName() );
  555. UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, szText );
  556. }
  557. else
  558. {
  559. // tell all spectators that the camera is available again.
  560. UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "SourceTV camera switched to auto-director mode." );
  561. }
  562. return true;
  563. }
  564. void CHLTVDirector::FinishCameraManShot()
  565. {
  566. Assert( m_iCameraMan == m_iPVSEntity );
  567. int index = FindFirstEvent( m_nBroadcastTick );
  568. if ( index == m_EventHistory.InvalidIndex() )
  569. {
  570. // check next frame again if event history is empty
  571. m_nNextShotTick = m_nBroadcastTick+1;
  572. return;
  573. }
  574. m_nNextShotTick = m_nBroadcastTick + TIME_TO_TICKS( MIN_SHOT_LENGTH );
  575. //check if camera turns camera off within broadcast time and game time
  576. while( index != m_EventHistory.InvalidIndex() )
  577. {
  578. CHLTVGameEvent &dc = m_EventHistory[index];
  579. if ( dc.m_Tick >= m_nNextShotTick )
  580. break;
  581. if ( Q_strcmp( dc.m_Event->GetName(), "hltv_cameraman") == 0 )
  582. {
  583. int iNewCameraMan = dc.m_Event->GetInt("index");
  584. if ( iNewCameraMan == 0 )
  585. {
  586. // camera man switched camera off
  587. m_nNextShotTick = dc.m_Tick+1;
  588. m_iCameraMan = 0;
  589. return;
  590. }
  591. }
  592. index = m_EventHistory.NextInorder( index );
  593. }
  594. // camera man is still recording and live, resend camera man message
  595. IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_cameraman", true );
  596. if ( msg )
  597. {
  598. msg->SetInt("index", m_iCameraMan );
  599. m_pHLTVServer->BroadcastEvent( msg );
  600. gameeventmanager->FreeEvent( msg );
  601. }
  602. }
  603. bool CHLTVDirector::StartCameraManShot()
  604. {
  605. Assert( m_nNextShotTick <= m_nBroadcastTick );
  606. int index = FindFirstEvent( m_nNextShotTick );
  607. // check for cameraman mode
  608. while( index != m_EventHistory.InvalidIndex() )
  609. {
  610. CHLTVGameEvent &dc = m_EventHistory[index];
  611. // only check if this is the current tick
  612. if ( dc.m_Tick > m_nBroadcastTick )
  613. break;
  614. if ( Q_strcmp( dc.m_Event->GetName(), "hltv_cameraman") == 0 )
  615. {
  616. if ( dc.m_Event->GetInt("index") > 0 )
  617. {
  618. // ok, this guy is now the active camera man
  619. m_iCameraMan = dc.m_Event->GetInt("index");
  620. m_iPVSEntity = m_iCameraMan;
  621. m_nNextShotTick = m_nBroadcastTick+1; // check setting right on next frame
  622. // send camera man command to client
  623. m_pHLTVServer->BroadcastEvent( dc.m_Event );
  624. return true;
  625. }
  626. }
  627. index = m_EventHistory.NextInorder( index );
  628. }
  629. return false; // no camera man found
  630. }
  631. void CHLTVDirector::StartInstantBroadcastShot()
  632. {
  633. m_nNextShotTick = m_nBroadcastTick + TIME_TO_TICKS( MAX_SHOT_LENGTH );
  634. if ( m_iCameraManIndex > 0 )
  635. {
  636. // camera man is still recording and live, resend camera man message
  637. IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_cameraman", true );
  638. if ( msg )
  639. {
  640. msg->SetInt("index", m_iCameraManIndex );
  641. m_pHLTVServer->BroadcastEvent( msg );
  642. gameeventmanager->FreeEvent( msg );
  643. m_iPVSEntity = m_iCameraManIndex;
  644. m_nNextShotTick = m_nBroadcastTick+TIME_TO_TICKS( MIN_SHOT_LENGTH );
  645. }
  646. }
  647. else
  648. {
  649. RemoveEventsFromHistory(-1); // all
  650. AnalyzePlayers();
  651. AnalyzeCameras();
  652. StartRandomShot();
  653. }
  654. }
  655. void CHLTVDirector::StartNewShot()
  656. {
  657. // we can remove all events the
  658. int smallestTick = MAX(0, gpGlobals->tickcount - TIME_TO_TICKS(HLTV_MAX_DELAY) );
  659. RemoveEventsFromHistory( smallestTick );
  660. // if the delay time is to short for autodirector, just show next best thing
  661. if ( m_fDelay < HLTV_MIN_DIRECTOR_DELAY )
  662. {
  663. StartInstantBroadcastShot();
  664. return;
  665. }
  666. if ( m_iCameraMan > 0 )
  667. {
  668. // we already have an active camera man,
  669. // wait until he releases the "record" lock
  670. FinishCameraManShot();
  671. return;
  672. }
  673. if ( StartCameraManShot() )
  674. {
  675. // now we have an active camera man
  676. return;
  677. }
  678. // ok, no camera man active, now check how much time
  679. // we have for the next shot, if the time diff to the next
  680. // important event we have to switch to is too short (<2sec)
  681. // just extent the current shot and don't start a new one
  682. // check the next 8 seconds for interrupts/important events
  683. m_nNextShotTick = m_nBroadcastTick + TIME_TO_TICKS( MAX_SHOT_LENGTH );
  684. if ( m_nBroadcastTick <= 0 )
  685. {
  686. // game hasn't started yet, we are still in the broadcast delay hole
  687. IGameEvent *msg = gameeventmanager->CreateEvent( "hltv_message", true );
  688. if ( msg )
  689. {
  690. msg->SetString("text", "Please wait for broadcast to start ..." );
  691. // send spectators the HLTV director command as a game event
  692. m_pHLTVServer->BroadcastEvent( msg );
  693. gameeventmanager->FreeEvent( msg );
  694. }
  695. StartBestFixedCameraShot( true );
  696. return;
  697. }
  698. int index = FindFirstEvent( m_nBroadcastTick );
  699. while( index != m_EventHistory.InvalidIndex() )
  700. {
  701. CHLTVGameEvent &dc = m_EventHistory[index];
  702. if ( dc.m_Tick >= m_nNextShotTick )
  703. break; // we have searched enough
  704. // a camera man is always interrupting auto director
  705. if ( Q_strcmp( dc.m_Event->GetName(), "hltv_cameraman") == 0 )
  706. {
  707. if ( dc.m_Event->GetInt("index") > 0 )
  708. {
  709. // stop the next cut when this cameraman starts recording
  710. m_nNextShotTick = dc.m_Tick;
  711. break;
  712. }
  713. }
  714. index = m_EventHistory.NextInorder( index );
  715. }
  716. float flDuration = TICKS_TO_TIME(m_nNextShotTick - m_nBroadcastTick);
  717. if ( flDuration < MIN_SHOT_LENGTH )
  718. return; // not enough time for a new shot
  719. // find the most interesting game event for next shot
  720. CHLTVGameEvent *dc = FindBestGameEvent();
  721. if ( dc )
  722. {
  723. // show the game event
  724. CreateShotFromEvent( dc );
  725. }
  726. else
  727. {
  728. // no interesting events found, start random shot
  729. StartRandomShot();
  730. }
  731. }
  732. CHLTVGameEvent *CHLTVDirector::FindBestGameEvent()
  733. {
  734. int bestEvent[4];
  735. int bestEventPrio[4];
  736. Q_memset( bestEvent, 0, sizeof(bestEvent) );
  737. Q_memset( bestEventPrio, 0, sizeof(bestEventPrio) );
  738. int index = FindFirstEvent( m_nBroadcastTick );
  739. // search for next 4 best events within next 8 seconds
  740. for (int i = 0; i<4; i ++)
  741. {
  742. bestEventPrio[i] = 0;
  743. bestEvent[i] = 0;
  744. int tillTick = m_nBroadcastTick + TIME_TO_TICKS( 2.0f*(1.0f+i) );
  745. if ( tillTick > m_nNextShotTick )
  746. break;
  747. // sum all action for the next time
  748. while ( index != m_EventHistory.InvalidIndex() )
  749. {
  750. CHLTVGameEvent &event = m_EventHistory[index];
  751. if ( event.m_Tick > tillTick )
  752. break;
  753. int priority = event.m_Priority;
  754. if ( priority > bestEventPrio[i] )
  755. {
  756. bestEvent[i] = index;
  757. bestEventPrio[i] = priority;
  758. }
  759. index = m_EventHistory.NextInorder( index );
  760. }
  761. }
  762. if ( !( bestEventPrio[0] || bestEventPrio[1] || bestEventPrio[2] ) )
  763. return NULL; // no event found at all, give generic algorithm a chance
  764. // camera cut rules :
  765. if ( bestEventPrio[1] >= bestEventPrio[0] &&
  766. bestEventPrio[1] >= bestEventPrio[2] &&
  767. bestEventPrio[1] >= bestEventPrio[3] )
  768. {
  769. return &m_EventHistory[ bestEvent[1] ]; // best case
  770. }
  771. else if ( bestEventPrio[0] > bestEventPrio[1] &&
  772. bestEventPrio[0] > bestEventPrio[2] )
  773. {
  774. return &m_EventHistory[ bestEvent[0] ]; // event 0 is very important
  775. }
  776. else if ( bestEventPrio[2] > bestEventPrio[3] )
  777. {
  778. return &m_EventHistory[ bestEvent[2] ];
  779. }
  780. else
  781. {
  782. // event 4 is the best but too far away, so show event 1
  783. if ( bestEvent[0] )
  784. return &m_EventHistory[ bestEvent[0] ];
  785. else
  786. return NULL;
  787. }
  788. }
  789. void CHLTVDirector::AnalyzeCameras()
  790. {
  791. InitRandomOrder( m_nNumFixedCameras );
  792. for ( int i = 0; i<m_nNumFixedCameras; i++ )
  793. {
  794. int iCameraIndex = s_RndOrder[i];
  795. CBaseEntity *pCamera = m_pFixedCameras[ iCameraIndex ];
  796. float flRank = 0.0f;
  797. int iClosestPlayer = 0;
  798. float flClosestPlayerDist = 100000.0f;
  799. int nCount = 0; // Number of visible targets
  800. Vector vDistribution; vDistribution.Init(); // distribution of targets
  801. Vector vCamPos = pCamera->GetAbsOrigin();
  802. for ( int j=0; j<m_nNumActivePlayers; j++ )
  803. {
  804. CBasePlayer *pPlayer = m_pActivePlayers[j];
  805. Vector vPlayerPos = pPlayer->GetAbsOrigin();
  806. float dist = VectorLength( vPlayerPos - vCamPos );
  807. if ( dist > 1024.0f || dist < 4.0f )
  808. continue; // too colse or far away
  809. // check visibility
  810. trace_t tr;
  811. UTIL_TraceLine( vCamPos, pPlayer->GetAbsOrigin(), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
  812. if ( tr.fraction < 1.0 )
  813. continue; // not visible for camera
  814. nCount++;
  815. // remember closest player
  816. if ( dist < flClosestPlayerDist )
  817. {
  818. iClosestPlayer = pPlayer->entindex();
  819. flClosestPlayerDist = dist;
  820. }
  821. Vector v1; AngleVectors( pPlayer->EyeAngles(), &v1 );
  822. // check players orientation towards camera
  823. Vector v2 = vCamPos - vPlayerPos;
  824. VectorNormalize( v2 );
  825. // player/camera cost function:
  826. flRank += ( 1.0f/sqrt(dist) ) * WeightedAngle( v1, v2 );
  827. vDistribution += v2;
  828. }
  829. if ( nCount > 0 )
  830. {
  831. // normalize distribution
  832. flRank *= VectorLength( vDistribution ) / nCount;
  833. }
  834. IGameEvent *event = gameeventmanager->CreateEvent("hltv_rank_camera");
  835. if ( event )
  836. {
  837. event->SetFloat("rank", flRank );
  838. event->SetInt("index", iCameraIndex ); // index in m_pFixedCameras
  839. event->SetInt("target", iClosestPlayer ); // ent index
  840. gameeventmanager->FireEvent( event );
  841. }
  842. }
  843. }
  844. void CHLTVDirector::BuildActivePlayerList()
  845. {
  846. // first build list of all active players
  847. m_nNumActivePlayers = 0;
  848. for ( int i =1; i <= gpGlobals->maxClients; i++ )
  849. {
  850. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  851. if ( !pPlayer )
  852. continue;
  853. if ( !pPlayer->IsAlive() )
  854. continue;
  855. if ( pPlayer->IsObserver() )
  856. continue;
  857. if ( pPlayer->GetTeamNumber() <= TEAM_SPECTATOR )
  858. continue;
  859. m_pActivePlayers[m_nNumActivePlayers] = pPlayer;
  860. m_nNumActivePlayers++;
  861. }
  862. }
  863. void CHLTVDirector::AnalyzePlayers()
  864. {
  865. // build list of current active players
  866. BuildActivePlayerList();
  867. // analyzes every active player
  868. InitRandomOrder( m_nNumActivePlayers );
  869. for ( int i = 0; i<m_nNumActivePlayers; i++ )
  870. {
  871. int iPlayerIndex = s_RndOrder[i];
  872. CBasePlayer *pPlayer = m_pActivePlayers[ iPlayerIndex ];
  873. float flRank = 0.0f;
  874. int iBestFacingPlayer = 0;
  875. float flBestFacingPlayer = 0.0f;
  876. int nCount = 0; // Number of visible targets
  877. Vector vDistribution; vDistribution.Init(); // distribution of targets
  878. Vector vCamPos = pPlayer->GetAbsOrigin();
  879. Vector v1; AngleVectors( pPlayer->EyeAngles(), &v1 );
  880. v1 *= -1; // inverted
  881. for ( int j=0; j<m_nNumActivePlayers; j++ )
  882. {
  883. if ( iPlayerIndex == j )
  884. continue; // don't check against itself
  885. CBasePlayer *pOtherPlayer = m_pActivePlayers[j];
  886. Vector vPlayerPos = pOtherPlayer->GetAbsOrigin();
  887. float dist = VectorLength( vPlayerPos - vCamPos );
  888. if ( dist > 1024.0f || dist < 4.0f )
  889. continue; // too close or far away
  890. // check visibility
  891. trace_t tr;
  892. UTIL_TraceLine( vCamPos, pOtherPlayer->GetAbsOrigin(), MASK_SOLID, pOtherPlayer, COLLISION_GROUP_NONE, &tr );
  893. if ( tr.fraction < 1.0 )
  894. continue; // not visible for camera
  895. nCount++;
  896. // check players orientation towards camera
  897. Vector v2; AngleVectors( pOtherPlayer->EyeAngles(), &v2 );
  898. float facing = WeightedAngle( v1, v2 );
  899. // remember closest player
  900. if ( facing > flBestFacingPlayer )
  901. {
  902. iBestFacingPlayer = pOtherPlayer->entindex();
  903. flBestFacingPlayer = facing;
  904. }
  905. // player/camera cost function:
  906. flRank += ( 1.0f/sqrt(dist) ) * facing;
  907. vDistribution += v2;
  908. }
  909. if ( nCount > 0 )
  910. {
  911. float flDistribution = VectorLength( vDistribution ) / nCount; // normalize distribution
  912. flRank *= flDistribution;
  913. }
  914. IGameEvent *event = gameeventmanager->CreateEvent("hltv_rank_entity");
  915. if ( event )
  916. {
  917. event->SetInt("index", pPlayer->entindex() );
  918. event->SetFloat("rank", flRank );
  919. event->SetInt("target", iBestFacingPlayer ); // ent index
  920. gameeventmanager->FireEvent( event );
  921. }
  922. }
  923. }