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.

1651 lines
49 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: The system for handling director's commentary style production info in-game.
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #ifndef _XBOX
  9. #include "tier0/icommandline.h"
  10. #include "igamesystem.h"
  11. #include "filesystem.h"
  12. #include <KeyValues.h>
  13. #include "in_buttons.h"
  14. #include "engine/IEngineSound.h"
  15. #include "soundenvelope.h"
  16. #include "utldict.h"
  17. #include "isaverestore.h"
  18. #include "eventqueue.h"
  19. #include "saverestore_utlvector.h"
  20. #include "gamestats.h"
  21. #include "ai_basenpc.h"
  22. #include "Sprite.h"
  23. // memdbgon must be the last include file in a .cpp file!!!
  24. #include "tier0/memdbgon.h"
  25. static bool g_bTracingVsCommentaryNodes = false;
  26. static const char *s_pCommentaryUpdateViewThink = "CommentaryUpdateViewThink";
  27. #define COMMENTARY_SPAWNED_SEMAPHORE "commentary_semaphore"
  28. extern ConVar commentary;
  29. ConVar commentary_available("commentary_available", "0", FCVAR_NONE, "Automatically set by the game when a commentary file is available for the current map." );
  30. enum teleport_stages_t
  31. {
  32. TELEPORT_NONE,
  33. TELEPORT_FADEOUT,
  34. TELEPORT_TELEPORT,
  35. TELEPORT_FADEIN,
  36. };
  37. // Convar restoration save/restore
  38. #define MAX_MODIFIED_CONVAR_STRING 128
  39. struct modifiedconvars_t
  40. {
  41. DECLARE_SIMPLE_DATADESC();
  42. char pszConvar[MAX_MODIFIED_CONVAR_STRING];
  43. char pszCurrentValue[MAX_MODIFIED_CONVAR_STRING];
  44. char pszOrgValue[MAX_MODIFIED_CONVAR_STRING];
  45. };
  46. bool g_bInCommentaryMode = false;
  47. bool IsInCommentaryMode( void )
  48. {
  49. return g_bInCommentaryMode;
  50. }
  51. //-----------------------------------------------------------------------------
  52. // Purpose: An entity that marks a spot for a piece of commentary
  53. //-----------------------------------------------------------------------------
  54. class CPointCommentaryNode : public CBaseAnimating
  55. {
  56. DECLARE_CLASS( CPointCommentaryNode, CBaseAnimating );
  57. public:
  58. DECLARE_DATADESC();
  59. DECLARE_SERVERCLASS();
  60. void Spawn( void );
  61. void Precache( void );
  62. void Activate( void );
  63. void SpinThink( void );
  64. void StartCommentary( void );
  65. void FinishCommentary( bool bBlendOut = true );
  66. void CleanupPostCommentary( void );
  67. void UpdateViewThink( void );
  68. void UpdateViewPostThink( void );
  69. bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace );
  70. bool HasViewTarget( void ) { return (m_hViewTarget != NULL || m_hViewPosition.Get() != NULL); }
  71. bool PreventsMovement( void );
  72. bool CannotBeStopped( void ) { return (m_bUnstoppable || m_bPreventChangesWhileMoving); }
  73. int UpdateTransmitState( void );
  74. void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
  75. void SetDisabled( bool bDisabled );
  76. void SetNodeNumber( int iCount ) { m_iNodeNumber = iCount; }
  77. // Called to tell the node when it's moved under/not-under the player's crosshair
  78. void SetUnderCrosshair( bool bUnderCrosshair );
  79. // Called when the player attempts to activate the node
  80. void PlayerActivated( void );
  81. void StopPlaying( void );
  82. void AbortPlaying( void );
  83. void TeleportTo( CBasePlayer *pPlayer );
  84. bool CanTeleportTo( void );
  85. // Inputs
  86. void InputStartCommentary( inputdata_t &inputdata );
  87. void InputStartUnstoppableCommentary( inputdata_t &inputdata );
  88. void InputEnable( inputdata_t &inputdata );
  89. void InputDisable( inputdata_t &inputdata );
  90. private:
  91. string_t m_iszPreCommands;
  92. string_t m_iszPostCommands;
  93. CNetworkVar( string_t, m_iszCommentaryFile );
  94. CNetworkVar( string_t, m_iszCommentaryFileNoHDR );
  95. string_t m_iszViewTarget;
  96. EHANDLE m_hViewTarget;
  97. EHANDLE m_hViewTargetAngles; // Entity used to blend view angles to look at the target
  98. string_t m_iszViewPosition;
  99. CNetworkVar( EHANDLE, m_hViewPosition );
  100. EHANDLE m_hViewPositionMover; // Entity used to blend the view to the viewposition entity
  101. bool m_bPreventMovement;
  102. bool m_bUnderCrosshair;
  103. bool m_bUnstoppable;
  104. float m_flFinishedTime;
  105. Vector m_vecFinishOrigin;
  106. QAngle m_vecOriginalAngles;
  107. QAngle m_vecFinishAngles;
  108. bool m_bPreventChangesWhileMoving;
  109. bool m_bDisabled;
  110. Vector m_vecTeleportOrigin;
  111. COutputEvent m_pOnCommentaryStarted;
  112. COutputEvent m_pOnCommentaryStopped;
  113. CNetworkVar( bool, m_bActive );
  114. CNetworkVar( float, m_flStartTime );
  115. CNetworkVar( string_t, m_iszSpeakers );
  116. CNetworkVar( int, m_iNodeNumber );
  117. CNetworkVar( int, m_iNodeNumberMax );
  118. };
  119. BEGIN_DATADESC( CPointCommentaryNode )
  120. DEFINE_KEYFIELD( m_iszPreCommands, FIELD_STRING, "precommands" ),
  121. DEFINE_KEYFIELD( m_iszPostCommands, FIELD_STRING, "postcommands" ),
  122. DEFINE_KEYFIELD( m_iszCommentaryFile, FIELD_STRING, "commentaryfile" ),
  123. DEFINE_KEYFIELD( m_iszCommentaryFileNoHDR, FIELD_STRING, "commentaryfile_nohdr" ),
  124. DEFINE_KEYFIELD( m_iszViewTarget, FIELD_STRING, "viewtarget" ),
  125. DEFINE_FIELD( m_hViewTarget, FIELD_EHANDLE ),
  126. DEFINE_FIELD( m_hViewTargetAngles, FIELD_EHANDLE ),
  127. DEFINE_KEYFIELD( m_iszViewPosition, FIELD_STRING, "viewposition" ),
  128. DEFINE_FIELD( m_hViewPosition, FIELD_EHANDLE ),
  129. DEFINE_FIELD( m_hViewPositionMover, FIELD_EHANDLE ),
  130. DEFINE_KEYFIELD( m_bPreventMovement, FIELD_BOOLEAN, "prevent_movement" ),
  131. DEFINE_FIELD( m_bUnderCrosshair, FIELD_BOOLEAN ),
  132. DEFINE_FIELD( m_bUnstoppable, FIELD_BOOLEAN ),
  133. DEFINE_FIELD( m_flFinishedTime, FIELD_TIME ),
  134. DEFINE_FIELD( m_vecFinishOrigin, FIELD_VECTOR ),
  135. DEFINE_FIELD( m_vecOriginalAngles, FIELD_VECTOR ),
  136. DEFINE_FIELD( m_vecFinishAngles, FIELD_VECTOR ),
  137. DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
  138. DEFINE_FIELD( m_flStartTime, FIELD_TIME ),
  139. DEFINE_KEYFIELD( m_iszSpeakers, FIELD_STRING, "speakers" ),
  140. DEFINE_FIELD( m_iNodeNumber, FIELD_INTEGER ),
  141. DEFINE_FIELD( m_iNodeNumberMax, FIELD_INTEGER ),
  142. DEFINE_FIELD( m_bPreventChangesWhileMoving, FIELD_BOOLEAN ),
  143. DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "start_disabled" ),
  144. DEFINE_KEYFIELD( m_vecTeleportOrigin, FIELD_VECTOR, "teleport_origin" ),
  145. // Outputs
  146. DEFINE_OUTPUT( m_pOnCommentaryStarted, "OnCommentaryStarted" ),
  147. DEFINE_OUTPUT( m_pOnCommentaryStopped, "OnCommentaryStopped" ),
  148. // Inputs
  149. DEFINE_INPUTFUNC( FIELD_VOID, "StartCommentary", InputStartCommentary ),
  150. DEFINE_INPUTFUNC( FIELD_VOID, "StartUnstoppableCommentary", InputStartUnstoppableCommentary ),
  151. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  152. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  153. // Functions
  154. DEFINE_THINKFUNC( SpinThink ),
  155. DEFINE_THINKFUNC( UpdateViewThink ),
  156. DEFINE_THINKFUNC( UpdateViewPostThink ),
  157. END_DATADESC()
  158. IMPLEMENT_SERVERCLASS_ST( CPointCommentaryNode, DT_PointCommentaryNode )
  159. SendPropBool( SENDINFO(m_bActive) ),
  160. SendPropStringT( SENDINFO(m_iszCommentaryFile) ),
  161. SendPropStringT( SENDINFO(m_iszCommentaryFileNoHDR) ),
  162. SendPropTime( SENDINFO(m_flStartTime) ),
  163. SendPropStringT( SENDINFO(m_iszSpeakers) ),
  164. SendPropInt( SENDINFO(m_iNodeNumber), 8, SPROP_UNSIGNED ),
  165. SendPropInt( SENDINFO(m_iNodeNumberMax), 8, SPROP_UNSIGNED ),
  166. SendPropEHandle( SENDINFO(m_hViewPosition) ),
  167. END_SEND_TABLE()
  168. LINK_ENTITY_TO_CLASS( point_commentary_node, CPointCommentaryNode );
  169. //-----------------------------------------------------------------------------
  170. // Laser Dot
  171. //-----------------------------------------------------------------------------
  172. class CCommentaryViewPosition : public CSprite
  173. {
  174. DECLARE_CLASS( CCommentaryViewPosition, CSprite );
  175. public:
  176. virtual void Spawn( void )
  177. {
  178. Precache();
  179. SetModelName( MAKE_STRING("sprites/redglow1.vmt") );
  180. BaseClass::Spawn();
  181. SetMoveType( MOVETYPE_NONE );
  182. AddSolidFlags( FSOLID_NOT_SOLID );
  183. AddEffects( EF_NOSHADOW );
  184. UTIL_SetSize( this, vec3_origin, vec3_origin );
  185. }
  186. virtual void Precache( void )
  187. {
  188. PrecacheModel( "sprites/redglow1.vmt" );
  189. }
  190. };
  191. LINK_ENTITY_TO_CLASS( point_commentary_viewpoint, CCommentaryViewPosition );
  192. //-----------------------------------------------------------------------------
  193. // Purpose: In multiplayer, always return player 1
  194. //-----------------------------------------------------------------------------
  195. CBasePlayer *GetCommentaryPlayer( void )
  196. {
  197. CBasePlayer *pPlayer;
  198. if ( gpGlobals->maxClients <= 1 )
  199. {
  200. pPlayer = UTIL_GetLocalPlayer();
  201. }
  202. else
  203. {
  204. // only respond to the first player
  205. pPlayer = UTIL_PlayerByIndex(1);
  206. }
  207. return pPlayer;
  208. }
  209. //===========================================================================================================
  210. // COMMENTARY GAME SYSTEM
  211. //===========================================================================================================
  212. void CV_GlobalChange_Commentary( IConVar *var, const char *pOldString, float flOldValue );
  213. //-----------------------------------------------------------------------------
  214. // Purpose: Game system to kickstart the director's commentary
  215. //-----------------------------------------------------------------------------
  216. class CCommentarySystem : public CAutoGameSystemPerFrame
  217. {
  218. public:
  219. DECLARE_DATADESC();
  220. CCommentarySystem() : CAutoGameSystemPerFrame( "CCommentarySystem" )
  221. {
  222. m_iCommentaryNodeCount = 0;
  223. }
  224. virtual void LevelInitPreEntity()
  225. {
  226. m_hCurrentNode = NULL;
  227. m_bCommentaryConvarsChanging = false;
  228. m_iClearPressedButtons = 0;
  229. // If the map started via the map_commentary cmd, start in commentary
  230. g_bInCommentaryMode = (engine->IsInCommentaryMode() != 0);
  231. CalculateCommentaryState();
  232. }
  233. void CalculateCommentaryState( void )
  234. {
  235. // Set the available cvar if we can find commentary data for this level
  236. char szFullName[512];
  237. Q_snprintf(szFullName,sizeof(szFullName), "maps/%s_commentary.txt", STRING( gpGlobals->mapname) );
  238. if ( filesystem->FileExists( szFullName ) )
  239. {
  240. commentary_available.SetValue( true );
  241. // If the user wanted commentary, kick it on
  242. if ( commentary.GetBool() )
  243. {
  244. g_bInCommentaryMode = true;
  245. }
  246. }
  247. else
  248. {
  249. g_bInCommentaryMode = false;
  250. commentary_available.SetValue( false );
  251. }
  252. }
  253. virtual void LevelShutdownPreEntity()
  254. {
  255. ShutDownCommentary();
  256. }
  257. void ParseEntKVBlock( CBaseEntity *pNode, KeyValues *pkvNode )
  258. {
  259. KeyValues *pkvNodeData = pkvNode->GetFirstSubKey();
  260. while ( pkvNodeData )
  261. {
  262. // Handle the connections block
  263. if ( !Q_strcmp(pkvNodeData->GetName(), "connections") )
  264. {
  265. ParseEntKVBlock( pNode, pkvNodeData );
  266. }
  267. else
  268. {
  269. #define COMMENTARY_STRING_LENGTH_MAX 1024
  270. const char *pszValue = pkvNodeData->GetString();
  271. Assert( Q_strlen(pszValue) < COMMENTARY_STRING_LENGTH_MAX );
  272. if ( Q_strnchr(pszValue, '^', COMMENTARY_STRING_LENGTH_MAX) )
  273. {
  274. // We want to support quotes in our strings so that we can specify multiple parameters in
  275. // an output inside our commentary files. We convert ^s to "s here.
  276. char szTmp[COMMENTARY_STRING_LENGTH_MAX];
  277. Q_strncpy( szTmp, pszValue, COMMENTARY_STRING_LENGTH_MAX );
  278. int len = Q_strlen( szTmp );
  279. for ( int i = 0; i < len; i++ )
  280. {
  281. if ( szTmp[i] == '^' )
  282. {
  283. szTmp[i] = '"';
  284. }
  285. }
  286. pNode->KeyValue( pkvNodeData->GetName(), szTmp );
  287. }
  288. else
  289. {
  290. pNode->KeyValue( pkvNodeData->GetName(), pszValue );
  291. }
  292. }
  293. pkvNodeData = pkvNodeData->GetNextKey();
  294. }
  295. }
  296. virtual void LevelInitPostEntity( void )
  297. {
  298. if ( !IsInCommentaryMode() )
  299. return;
  300. // Don't spawn commentary entities when loading a savegame
  301. if ( gpGlobals->eLoadType == MapLoad_LoadGame || gpGlobals->eLoadType == MapLoad_Background )
  302. return;
  303. m_bCommentaryEnabledMidGame = false;
  304. InitCommentary();
  305. IGameEvent *event = gameeventmanager->CreateEvent( "playing_commentary" );
  306. gameeventmanager->FireEventClientSide( event );
  307. }
  308. CPointCommentaryNode *GetNodeUnderCrosshair()
  309. {
  310. CBasePlayer *pPlayer = GetCommentaryPlayer();
  311. if ( !pPlayer )
  312. return NULL;
  313. // See if the player's looking at a commentary node
  314. trace_t tr;
  315. Vector vecSrc = pPlayer->EyePosition();
  316. Vector vecForward = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DIRECT_ONLY );
  317. g_bTracingVsCommentaryNodes = true;
  318. UTIL_TraceLine( vecSrc, vecSrc + vecForward * MAX_TRACE_LENGTH, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
  319. g_bTracingVsCommentaryNodes = false;
  320. if ( !tr.m_pEnt )
  321. return NULL;
  322. return dynamic_cast<CPointCommentaryNode*>(tr.m_pEnt);
  323. }
  324. void PrePlayerRunCommand( CBasePlayer *pPlayer, CUserCmd *pUserCmds )
  325. {
  326. if ( !IsInCommentaryMode() )
  327. return;
  328. if ( pPlayer->IsFakeClient() )
  329. return;
  330. CPointCommentaryNode *pCurrentNode = GetNodeUnderCrosshair();
  331. // Changed nodes?
  332. if ( m_hCurrentNode != pCurrentNode )
  333. {
  334. // Stop animating the old one
  335. if ( m_hCurrentNode.Get() )
  336. {
  337. m_hCurrentNode->SetUnderCrosshair( false );
  338. }
  339. // Start animating the new one
  340. if ( pCurrentNode )
  341. {
  342. pCurrentNode->SetUnderCrosshair( true );
  343. }
  344. m_hCurrentNode = pCurrentNode;
  345. }
  346. // Check for commentary node activations
  347. if ( pPlayer )
  348. {
  349. // Has the player pressed down an attack button?
  350. int buttonsChanged = m_afPlayersLastButtons ^ pUserCmds->buttons;
  351. int buttonsPressed = buttonsChanged & pUserCmds->buttons;
  352. m_afPlayersLastButtons = pUserCmds->buttons;
  353. if ( !(pUserCmds->buttons & COMMENTARY_BUTTONS) )
  354. {
  355. m_iClearPressedButtons &= ~COMMENTARY_BUTTONS;
  356. }
  357. // Detect press events to start/stop commentary nodes
  358. if (buttonsPressed & COMMENTARY_BUTTONS)
  359. {
  360. if ( buttonsPressed & IN_ATTACK2 )
  361. {
  362. if ( !(GetActiveNode() && GetActiveNode()->CannotBeStopped()) )
  363. {
  364. JumpToNextNode( pPlayer );
  365. pUserCmds->buttons &= ~COMMENTARY_BUTTONS;
  366. m_iClearPressedButtons |= (buttonsPressed & COMMENTARY_BUTTONS);
  367. }
  368. }
  369. else
  370. {
  371. // Looking at a node?
  372. if ( m_hCurrentNode )
  373. {
  374. // Ignore input while an unstoppable node is playing
  375. if ( !GetActiveNode() || !GetActiveNode()->CannotBeStopped() )
  376. {
  377. // If we have an active node already, stop it
  378. if ( GetActiveNode() && GetActiveNode() != m_hCurrentNode )
  379. {
  380. GetActiveNode()->StopPlaying();
  381. }
  382. m_hCurrentNode->PlayerActivated();
  383. }
  384. // Prevent weapon firing when toggling nodes
  385. pUserCmds->buttons &= ~COMMENTARY_BUTTONS;
  386. m_iClearPressedButtons |= (buttonsPressed & COMMENTARY_BUTTONS);
  387. }
  388. else if ( GetActiveNode() && GetActiveNode()->HasViewTarget() )
  389. {
  390. if ( !GetActiveNode()->CannotBeStopped() )
  391. {
  392. GetActiveNode()->StopPlaying();
  393. }
  394. // Prevent weapon firing when toggling nodes
  395. pUserCmds->buttons &= ~COMMENTARY_BUTTONS;
  396. m_iClearPressedButtons |= (buttonsPressed & COMMENTARY_BUTTONS);
  397. }
  398. }
  399. }
  400. if ( GetActiveNode() && GetActiveNode()->PreventsMovement() )
  401. {
  402. pUserCmds->buttons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP | IN_DUCK );
  403. pUserCmds->upmove = 0;
  404. pUserCmds->sidemove = 0;
  405. pUserCmds->forwardmove = 0;
  406. }
  407. // When we swallow button down events, we have to keep clearing that button
  408. // until the player releases the button. Otherwise, the frame after we swallow
  409. // it, the code detects the button down and goes ahead as normal.
  410. pUserCmds->buttons &= ~m_iClearPressedButtons;
  411. }
  412. if ( m_iTeleportStage != TELEPORT_NONE )
  413. {
  414. if ( m_flNextTeleportTime <= gpGlobals->curtime )
  415. {
  416. if ( m_iTeleportStage == TELEPORT_FADEOUT )
  417. {
  418. m_iTeleportStage = TELEPORT_TELEPORT;
  419. m_flNextTeleportTime = gpGlobals->curtime + 0.35;
  420. color32_s clr = { 0,0,0,255 };
  421. UTIL_ScreenFade( pPlayer, clr, 0.3, 0, FFADE_OUT | FFADE_PURGE | FFADE_STAYOUT );
  422. }
  423. else if ( m_iTeleportStage == TELEPORT_TELEPORT )
  424. {
  425. if ( m_hLastCommentaryNode )
  426. {
  427. m_hLastCommentaryNode->TeleportTo( pPlayer );
  428. }
  429. m_iTeleportStage = TELEPORT_FADEIN;
  430. m_flNextTeleportTime = gpGlobals->curtime + 0.6;
  431. }
  432. else if ( m_iTeleportStage == TELEPORT_FADEIN )
  433. {
  434. m_iTeleportStage = TELEPORT_NONE;
  435. m_flNextTeleportTime = gpGlobals->curtime + 0.25;
  436. color32_s clr = { 0,0,0,255 };
  437. UTIL_ScreenFade( pPlayer, clr, 0.3, 0, FFADE_IN | FFADE_PURGE );
  438. }
  439. }
  440. }
  441. }
  442. CPointCommentaryNode *GetActiveNode( void )
  443. {
  444. return m_hActiveCommentaryNode;
  445. }
  446. void SetActiveNode( CPointCommentaryNode *pNode )
  447. {
  448. m_hActiveCommentaryNode = pNode;
  449. if ( pNode )
  450. {
  451. m_hLastCommentaryNode = pNode;
  452. }
  453. }
  454. int GetCommentaryNodeCount( void )
  455. {
  456. return m_iCommentaryNodeCount;
  457. }
  458. bool CommentaryConvarsChanging( void )
  459. {
  460. return m_bCommentaryConvarsChanging;
  461. }
  462. void SetCommentaryConvarsChanging( bool bChanging )
  463. {
  464. m_bCommentaryConvarsChanging = bChanging;
  465. }
  466. void ConvarChanged( IConVar *pConVar, const char *pOldString, float flOldValue )
  467. {
  468. ConVarRef var( pConVar );
  469. // A convar has been changed by a commentary node. We need to store
  470. // the old state. If the engine shuts down, we need to restore any
  471. // convars that the commentary changed to their previous values.
  472. for ( int i = 0; i < m_ModifiedConvars.Count(); i++ )
  473. {
  474. // If we find it, just update the current value
  475. if ( !Q_strncmp( var.GetName(), m_ModifiedConvars[i].pszConvar, MAX_MODIFIED_CONVAR_STRING ) )
  476. {
  477. Q_strncpy( m_ModifiedConvars[i].pszCurrentValue, var.GetString(), MAX_MODIFIED_CONVAR_STRING );
  478. //Msg(" Updating Convar %s: value %s (org %s)\n", m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue );
  479. return;
  480. }
  481. }
  482. // We didn't find it in our list, so add it
  483. modifiedconvars_t newConvar;
  484. Q_strncpy( newConvar.pszConvar, var.GetName(), MAX_MODIFIED_CONVAR_STRING );
  485. Q_strncpy( newConvar.pszCurrentValue, var.GetString(), MAX_MODIFIED_CONVAR_STRING );
  486. Q_strncpy( newConvar.pszOrgValue, pOldString, MAX_MODIFIED_CONVAR_STRING );
  487. m_ModifiedConvars.AddToTail( newConvar );
  488. /*
  489. Msg(" Commentary changed '%s' to '%s' (was '%s')\n", var->GetName(), var->GetString(), pOldString );
  490. Msg(" Convars stored: %d\n", m_ModifiedConvars.Count() );
  491. for ( int i = 0; i < m_ModifiedConvars.Count(); i++ )
  492. {
  493. Msg(" Convar %d: %s, value %s (org %s)\n", i, m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue );
  494. }
  495. */
  496. }
  497. void InitCommentary( void )
  498. {
  499. // Install the global cvar callback
  500. cvar->InstallGlobalChangeCallback( CV_GlobalChange_Commentary );
  501. m_flNextTeleportTime = 0;
  502. m_iTeleportStage = TELEPORT_NONE;
  503. m_hLastCommentaryNode = NULL;
  504. // If we find the commentary semaphore, the commentary entities already exist.
  505. // This occurs when you transition back to a map that has saved commentary nodes in it.
  506. if ( gEntList.FindEntityByName( NULL, COMMENTARY_SPAWNED_SEMAPHORE ) )
  507. return;
  508. // Spawn the commentary semaphore entity
  509. CBaseEntity *pSemaphore = CreateEntityByName( "info_target" );
  510. pSemaphore->SetName( MAKE_STRING(COMMENTARY_SPAWNED_SEMAPHORE) );
  511. bool oldLock = engine->LockNetworkStringTables( false );
  512. // Find the commentary file
  513. char szFullName[512];
  514. Q_snprintf(szFullName,sizeof(szFullName), "maps/%s_commentary.txt", STRING( gpGlobals->mapname ));
  515. KeyValues *pkvFile = new KeyValues( "Commentary" );
  516. if ( pkvFile->LoadFromFile( filesystem, szFullName, "MOD" ) )
  517. {
  518. Msg( "Commentary: Loading commentary data from %s. \n", szFullName );
  519. // Load each commentary block, and spawn the entities
  520. KeyValues *pkvNode = pkvFile->GetFirstSubKey();
  521. while ( pkvNode )
  522. {
  523. // Get node name
  524. const char *pNodeName = pkvNode->GetName();
  525. // Skip the trackinfo
  526. if ( !Q_strncmp( pNodeName, "trackinfo", 9 ) )
  527. {
  528. pkvNode = pkvNode->GetNextKey();
  529. continue;
  530. }
  531. KeyValues *pClassname = pkvNode->FindKey( "classname" );
  532. if ( pClassname )
  533. {
  534. // Use the classname instead
  535. pNodeName = pClassname->GetString();
  536. }
  537. // Spawn the commentary entity
  538. CBaseEntity *pNode = CreateEntityByName( pNodeName );
  539. if ( pNode )
  540. {
  541. ParseEntKVBlock( pNode, pkvNode );
  542. DispatchSpawn( pNode );
  543. EHANDLE hHandle;
  544. hHandle = pNode;
  545. m_hSpawnedEntities.AddToTail( hHandle );
  546. CPointCommentaryNode *pCommNode = dynamic_cast<CPointCommentaryNode*>(pNode);
  547. if ( pCommNode )
  548. {
  549. m_iCommentaryNodeCount++;
  550. pCommNode->SetNodeNumber( m_iCommentaryNodeCount );
  551. }
  552. }
  553. else
  554. {
  555. Warning("Commentary: Failed to spawn commentary entity, type: '%s'\n", pNodeName );
  556. }
  557. // Move to next entity
  558. pkvNode = pkvNode->GetNextKey();
  559. }
  560. // Then activate all the entities
  561. for ( int i = 0; i < m_hSpawnedEntities.Count(); i++ )
  562. {
  563. m_hSpawnedEntities[i]->Activate();
  564. }
  565. }
  566. else
  567. {
  568. Msg( "Commentary: Could not find commentary data file '%s'. \n", szFullName );
  569. }
  570. engine->LockNetworkStringTables( oldLock );
  571. }
  572. void ShutDownCommentary( void )
  573. {
  574. if ( GetActiveNode() )
  575. {
  576. GetActiveNode()->AbortPlaying();
  577. }
  578. // Destroy all the entities created by commentary
  579. for ( int i = m_hSpawnedEntities.Count()-1; i >= 0; i-- )
  580. {
  581. if ( m_hSpawnedEntities[i] )
  582. {
  583. UTIL_Remove( m_hSpawnedEntities[i] );
  584. }
  585. }
  586. m_hSpawnedEntities.Purge();
  587. m_iCommentaryNodeCount = 0;
  588. // Remove the commentary semaphore
  589. CBaseEntity *pSemaphore = gEntList.FindEntityByName( NULL, COMMENTARY_SPAWNED_SEMAPHORE );
  590. if ( pSemaphore )
  591. {
  592. UTIL_Remove( pSemaphore );
  593. }
  594. // Remove our global convar callback
  595. cvar->RemoveGlobalChangeCallback( CV_GlobalChange_Commentary );
  596. // Reset any convars that have been changed by the commentary
  597. for ( int i = 0; i < m_ModifiedConvars.Count(); i++ )
  598. {
  599. ConVar *pConVar = (ConVar *)cvar->FindVar( m_ModifiedConvars[i].pszConvar );
  600. if ( pConVar )
  601. {
  602. pConVar->SetValue( m_ModifiedConvars[i].pszOrgValue );
  603. }
  604. }
  605. m_ModifiedConvars.Purge();
  606. m_hCurrentNode = NULL;
  607. m_hActiveCommentaryNode = NULL;
  608. m_hLastCommentaryNode = NULL;
  609. m_flNextTeleportTime = 0;
  610. m_iTeleportStage = TELEPORT_NONE;
  611. }
  612. void SetCommentaryMode( bool bCommentaryMode )
  613. {
  614. g_bInCommentaryMode = bCommentaryMode;
  615. CalculateCommentaryState();
  616. // If we're turning on commentary, create all the entities.
  617. if ( IsInCommentaryMode() )
  618. {
  619. m_bCommentaryEnabledMidGame = true;
  620. InitCommentary();
  621. }
  622. else
  623. {
  624. ShutDownCommentary();
  625. }
  626. }
  627. void OnRestore( void )
  628. {
  629. cvar->RemoveGlobalChangeCallback( CV_GlobalChange_Commentary );
  630. if ( !IsInCommentaryMode() )
  631. return;
  632. // Set any convars that have already been changed by the commentary before the save
  633. for ( int i = 0; i < m_ModifiedConvars.Count(); i++ )
  634. {
  635. ConVar *pConVar = (ConVar *)cvar->FindVar( m_ModifiedConvars[i].pszConvar );
  636. if ( pConVar )
  637. {
  638. //Msg(" Restoring Convar %s: value %s (org %s)\n", m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue );
  639. pConVar->SetValue( m_ModifiedConvars[i].pszCurrentValue );
  640. }
  641. }
  642. // Install the global cvar callback
  643. cvar->InstallGlobalChangeCallback( CV_GlobalChange_Commentary );
  644. }
  645. bool CommentaryWasEnabledMidGame( void )
  646. {
  647. return m_bCommentaryEnabledMidGame;
  648. }
  649. void JumpToNextNode( CBasePlayer *pPlayer )
  650. {
  651. if ( m_flNextTeleportTime > gpGlobals->curtime || m_iTeleportStage != TELEPORT_NONE )
  652. return;
  653. CBaseEntity *pEnt = m_hLastCommentaryNode;
  654. while ( ( pEnt = gEntList.FindEntityByClassname( pEnt, "point_commentary_node" ) ) != m_hLastCommentaryNode )
  655. {
  656. CPointCommentaryNode *pNode = dynamic_cast<CPointCommentaryNode *>( pEnt );
  657. if ( pNode && pNode->CanTeleportTo() )
  658. {
  659. m_iTeleportStage = TELEPORT_FADEOUT;
  660. m_hLastCommentaryNode = pNode;
  661. m_flNextTeleportTime = gpGlobals->curtime;
  662. // Stop any active nodes
  663. if ( m_hActiveCommentaryNode )
  664. {
  665. m_hActiveCommentaryNode->StopPlaying();
  666. }
  667. break;
  668. }
  669. }
  670. }
  671. private:
  672. int m_afPlayersLastButtons;
  673. int m_iCommentaryNodeCount;
  674. bool m_bCommentaryConvarsChanging;
  675. int m_iClearPressedButtons;
  676. bool m_bCommentaryEnabledMidGame;
  677. float m_flNextTeleportTime;
  678. int m_iTeleportStage;
  679. CUtlVector< modifiedconvars_t > m_ModifiedConvars;
  680. CUtlVector<EHANDLE> m_hSpawnedEntities;
  681. CHandle<CPointCommentaryNode> m_hCurrentNode;
  682. CHandle<CPointCommentaryNode> m_hActiveCommentaryNode;
  683. CHandle<CPointCommentaryNode> m_hLastCommentaryNode;
  684. };
  685. CCommentarySystem g_CommentarySystem;
  686. void CommentarySystem_PePlayerRunCommand( CBasePlayer *player, CUserCmd *ucmd )
  687. {
  688. g_CommentarySystem.PrePlayerRunCommand( player, ucmd );
  689. }
  690. BEGIN_DATADESC_NO_BASE( CCommentarySystem )
  691. //int m_afPlayersLastButtons; DON'T SAVE
  692. //bool m_bCommentaryConvarsChanging; DON'T SAVE
  693. //int m_iClearPressedButtons; DON'T SAVE
  694. DEFINE_FIELD( m_bCommentaryEnabledMidGame, FIELD_BOOLEAN ),
  695. DEFINE_FIELD( m_flNextTeleportTime, FIELD_TIME ),
  696. DEFINE_FIELD( m_iTeleportStage, FIELD_INTEGER ),
  697. DEFINE_UTLVECTOR( m_ModifiedConvars, FIELD_EMBEDDED ),
  698. DEFINE_UTLVECTOR( m_hSpawnedEntities, FIELD_EHANDLE ),
  699. DEFINE_FIELD( m_hCurrentNode, FIELD_EHANDLE ),
  700. DEFINE_FIELD( m_hActiveCommentaryNode, FIELD_EHANDLE ),
  701. DEFINE_FIELD( m_hLastCommentaryNode, FIELD_EHANDLE ),
  702. DEFINE_FIELD( m_iCommentaryNodeCount, FIELD_INTEGER ),
  703. END_DATADESC()
  704. BEGIN_SIMPLE_DATADESC( modifiedconvars_t )
  705. DEFINE_ARRAY( pszConvar, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ),
  706. DEFINE_ARRAY( pszCurrentValue, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ),
  707. DEFINE_ARRAY( pszOrgValue, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ),
  708. END_DATADESC()
  709. //-----------------------------------------------------------------------------
  710. // Purpose:
  711. //-----------------------------------------------------------------------------
  712. void CC_CommentaryChanged( IConVar *pConVar, const char *pOldString, float flOldValue )
  713. {
  714. ConVarRef var( pConVar );
  715. if ( var.GetBool() != g_bInCommentaryMode )
  716. {
  717. g_CommentarySystem.SetCommentaryMode( var.GetBool() );
  718. }
  719. }
  720. ConVar commentary( "commentary", "0", FCVAR_NONE, "Desired commentary mode state.", CC_CommentaryChanged );
  721. //-----------------------------------------------------------------------------
  722. // Purpose: We need to revert back any convar changes that are made by the
  723. // commentary system during commentary. This code stores convar changes
  724. // made by the commentary system, and reverts them when finished.
  725. //-----------------------------------------------------------------------------
  726. void CV_GlobalChange_Commentary( IConVar *var, const char *pOldString, float flOldValue )
  727. {
  728. if ( !g_CommentarySystem.CommentaryConvarsChanging() )
  729. {
  730. // A convar has changed, but not due to commentary nodes. Ignore it.
  731. return;
  732. }
  733. g_CommentarySystem.ConvarChanged( var, pOldString, flOldValue );
  734. }
  735. //-----------------------------------------------------------------------------
  736. // Purpose:
  737. //-----------------------------------------------------------------------------
  738. void CC_CommentaryNotChanging( void )
  739. {
  740. g_CommentarySystem.SetCommentaryConvarsChanging( false );
  741. }
  742. static ConCommand commentary_cvarsnotchanging("commentary_cvarsnotchanging", CC_CommentaryNotChanging, 0 );
  743. bool IsListeningToCommentary( void )
  744. {
  745. return ( g_CommentarySystem.GetActiveNode() != NULL );
  746. }
  747. //===========================================================================================================
  748. // COMMENTARY NODES
  749. //===========================================================================================================
  750. //-----------------------------------------------------------------------------
  751. // Purpose:
  752. //-----------------------------------------------------------------------------
  753. void CPointCommentaryNode::Spawn( void )
  754. {
  755. // No model specified?
  756. char *szModel = (char *)STRING( GetModelName() );
  757. if (!szModel || !*szModel)
  758. {
  759. szModel = "models/extras/info_speech.mdl";
  760. SetModelName( AllocPooledString(szModel) );
  761. }
  762. Precache();
  763. SetModel( szModel );
  764. UTIL_SetSize( this, -Vector(16,16,16), Vector(16,16,16) );
  765. SetSolid( SOLID_BBOX );
  766. AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST );
  767. AddEffects( EF_NOSHADOW );
  768. // Setup for animation
  769. ResetSequence( LookupSequence("idle") );
  770. SetThink( &CPointCommentaryNode::SpinThink );
  771. SetNextThink( gpGlobals->curtime + 0.1f );
  772. m_iNodeNumber = 0;
  773. m_iNodeNumberMax = 0;
  774. SetDisabled( m_bDisabled );
  775. }
  776. //-----------------------------------------------------------------------------
  777. // Purpose:
  778. //-----------------------------------------------------------------------------
  779. void CPointCommentaryNode::Activate( void )
  780. {
  781. m_iNodeNumberMax = g_CommentarySystem.GetCommentaryNodeCount();
  782. if ( m_iszViewTarget != NULL_STRING )
  783. {
  784. m_hViewTarget = gEntList.FindEntityByName( NULL, m_iszViewTarget );
  785. if ( !m_hViewTarget )
  786. {
  787. Warning("%s: %s could not find viewtarget %s.\n", GetClassname(), GetDebugName(), STRING(m_iszViewTarget) );
  788. }
  789. }
  790. if ( m_iszViewPosition != NULL_STRING )
  791. {
  792. m_hViewPosition = gEntList.FindEntityByName( NULL, m_iszViewPosition );
  793. if ( !m_hViewPosition.Get() )
  794. {
  795. Warning("%s: %s could not find viewposition %s.\n", GetClassname(), GetDebugName(), STRING(m_iszViewPosition) );
  796. }
  797. }
  798. BaseClass::Activate();
  799. }
  800. //-----------------------------------------------------------------------------
  801. // Purpose:
  802. //-----------------------------------------------------------------------------
  803. void CPointCommentaryNode::Precache()
  804. {
  805. PrecacheModel( STRING( GetModelName() ) );
  806. if ( m_iszCommentaryFile.Get() != NULL_STRING )
  807. {
  808. PrecacheScriptSound( STRING( m_iszCommentaryFile.Get() ) );
  809. }
  810. else
  811. {
  812. Warning("%s: %s has no commentary file.\n", GetClassname(), GetDebugName() );
  813. }
  814. if ( m_iszCommentaryFileNoHDR.Get() != NULL_STRING )
  815. {
  816. PrecacheScriptSound( STRING( m_iszCommentaryFileNoHDR.Get() ) );
  817. }
  818. BaseClass::Precache();
  819. }
  820. //-----------------------------------------------------------------------------
  821. // Purpose: Called to tell the node when it's moved under/not-under the player's crosshair
  822. //-----------------------------------------------------------------------------
  823. void CPointCommentaryNode::SetUnderCrosshair( bool bUnderCrosshair )
  824. {
  825. if ( bUnderCrosshair )
  826. {
  827. // Start animating
  828. m_bUnderCrosshair = true;
  829. if ( !m_bActive )
  830. {
  831. m_flAnimTime = gpGlobals->curtime;
  832. }
  833. }
  834. else
  835. {
  836. // Stop animating
  837. m_bUnderCrosshair = false;
  838. }
  839. }
  840. //------------------------------------------------------------------------------
  841. // Purpose: Prevent collisions of everything except the trace from the commentary system
  842. //------------------------------------------------------------------------------
  843. bool CPointCommentaryNode::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
  844. {
  845. if ( !g_bTracingVsCommentaryNodes )
  846. return false;
  847. if ( m_bDisabled )
  848. return false;
  849. return BaseClass::TestCollision( ray, mask, trace );
  850. }
  851. //------------------------------------------------------------------------------
  852. // Purpose:
  853. //------------------------------------------------------------------------------
  854. void CPointCommentaryNode::SpinThink( void )
  855. {
  856. // Rotate if we're active, or under the crosshair. Don't rotate if we're
  857. // under the crosshair, but we've already been listened to.
  858. if ( m_bActive || (m_bUnderCrosshair && m_nSkin == 0) )
  859. {
  860. if ( m_bActive )
  861. {
  862. m_flPlaybackRate = 3.0;
  863. }
  864. else
  865. {
  866. m_flPlaybackRate = 1.0;
  867. }
  868. StudioFrameAdvance();
  869. DispatchAnimEvents(this);
  870. }
  871. SetNextThink( gpGlobals->curtime + 0.1f );
  872. }
  873. //------------------------------------------------------------------------------
  874. // Purpose:
  875. //------------------------------------------------------------------------------
  876. void CPointCommentaryNode::PlayerActivated( void )
  877. {
  878. gamestats->Event_Commentary();
  879. if ( m_bActive )
  880. {
  881. StopPlaying();
  882. }
  883. else
  884. {
  885. StartCommentary();
  886. g_CommentarySystem.SetActiveNode( this );
  887. }
  888. }
  889. //-----------------------------------------------------------------------------
  890. // Purpose:
  891. //-----------------------------------------------------------------------------
  892. void CPointCommentaryNode::StopPlaying( void )
  893. {
  894. if ( m_bActive )
  895. {
  896. FinishCommentary();
  897. }
  898. }
  899. //-----------------------------------------------------------------------------
  900. // Purpose: Stop playing the node, but snap completely out of the node.
  901. // Used when players shut down commentary while we're in the middle
  902. // of playing a node, so we can't smoothly blend out (since the
  903. // commentary entities need to be removed).
  904. //-----------------------------------------------------------------------------
  905. void CPointCommentaryNode::AbortPlaying( void )
  906. {
  907. if ( m_bActive )
  908. {
  909. FinishCommentary( false );
  910. }
  911. else if ( m_bPreventChangesWhileMoving )
  912. {
  913. // We're a node that's not active, but is in the process of transitioning the view. Finish movement.
  914. CleanupPostCommentary();
  915. }
  916. }
  917. //-----------------------------------------------------------------------------
  918. // Purpose:
  919. //-----------------------------------------------------------------------------
  920. bool CPointCommentaryNode::CanTeleportTo( void )
  921. {
  922. //return ( m_vecTeleportOrigin != vec3_origin );
  923. return true;
  924. }
  925. //-----------------------------------------------------------------------------
  926. // Purpose:
  927. //-----------------------------------------------------------------------------
  928. void CPointCommentaryNode::TeleportTo( CBasePlayer *pPlayer )
  929. {
  930. Vector vecTarget = m_vecTeleportOrigin;
  931. if ( m_vecTeleportOrigin == vec3_origin )
  932. {
  933. vecTarget = GetAbsOrigin();
  934. }
  935. trace_t trace;
  936. UTIL_TraceHull( vecTarget, vecTarget + Vector( 0, 0, -500 ), pPlayer->WorldAlignMins(), pPlayer->WorldAlignMaxs(), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &trace );
  937. pPlayer->Teleport( &trace.endpos, NULL, &vec3_origin );
  938. Vector vecToNode = GetAbsOrigin() - pPlayer->EyePosition();
  939. VectorNormalize( vecToNode );
  940. QAngle vecAngle;
  941. VectorAngles( vecToNode, Vector(0,0,1), vecAngle );
  942. pPlayer->SnapEyeAngles( vecAngle );
  943. }
  944. //------------------------------------------------------------------------------
  945. // Purpose:
  946. //------------------------------------------------------------------------------
  947. void CPointCommentaryNode::StartCommentary( void )
  948. {
  949. CBasePlayer *pPlayer = GetCommentaryPlayer();
  950. if ( !pPlayer )
  951. return;
  952. m_bActive = true;
  953. m_flAnimTime = gpGlobals->curtime;
  954. m_flPrevAnimTime = gpGlobals->curtime;
  955. // Switch to the greyed out skin
  956. m_nSkin = 1;
  957. m_pOnCommentaryStarted.FireOutput( this, this );
  958. // Fire off our precommands
  959. if ( m_iszPreCommands != NULL_STRING )
  960. {
  961. g_CommentarySystem.SetCommentaryConvarsChanging( true );
  962. engine->ClientCommand( pPlayer->edict(), STRING(m_iszPreCommands) );
  963. engine->ClientCommand( pPlayer->edict(), "commentary_cvarsnotchanging\n" );
  964. }
  965. // Start the commentary
  966. m_flStartTime = gpGlobals->curtime;
  967. // If we have a view target, start blending towards it
  968. if ( m_hViewTarget || m_hViewPosition.Get() )
  969. {
  970. m_vecOriginalAngles = pPlayer->EyeAngles();
  971. SetContextThink( &CPointCommentaryNode::UpdateViewThink, gpGlobals->curtime, s_pCommentaryUpdateViewThink );
  972. }
  973. //SetContextThink( &CPointCommentaryNode::FinishCommentary, gpGlobals->curtime + flDuration, s_pFinishCommentaryThink );
  974. }
  975. //-----------------------------------------------------------------------------
  976. // Purpose:
  977. //-----------------------------------------------------------------------------
  978. void CC_CommentaryFinishNode( void )
  979. {
  980. // We were told by the client DLL that our commentary has finished
  981. if ( g_CommentarySystem.GetActiveNode() )
  982. {
  983. g_CommentarySystem.GetActiveNode()->StopPlaying();
  984. }
  985. }
  986. static ConCommand commentary_finishnode("commentary_finishnode", CC_CommentaryFinishNode, 0 );
  987. //-----------------------------------------------------------------------------
  988. // Purpose:
  989. //-----------------------------------------------------------------------------
  990. void CPointCommentaryNode::UpdateViewThink( void )
  991. {
  992. if ( !m_bActive )
  993. return;
  994. CBasePlayer *pPlayer = GetCommentaryPlayer();
  995. if ( !pPlayer )
  996. return;
  997. // Swing the view towards the target
  998. if ( m_hViewTarget )
  999. {
  1000. if ( !m_hViewTargetAngles && !m_hViewPositionMover )
  1001. {
  1002. // Make an invisible entity to attach view angles to
  1003. m_hViewTargetAngles = CreateEntityByName( "point_commentary_viewpoint" );
  1004. m_hViewTargetAngles->SetAbsOrigin( pPlayer->EyePosition() );
  1005. m_hViewTargetAngles->SetAbsAngles( pPlayer->EyeAngles() );
  1006. pPlayer->SetViewEntity( m_hViewTargetAngles );
  1007. if ( pPlayer->GetActiveWeapon() )
  1008. {
  1009. pPlayer->GetActiveWeapon()->Holster();
  1010. }
  1011. }
  1012. QAngle angGoal;
  1013. QAngle angCurrent;
  1014. if ( m_hViewPositionMover )
  1015. {
  1016. angCurrent = m_hViewPositionMover->GetAbsAngles();
  1017. VectorAngles( m_hViewTarget->WorldSpaceCenter() - m_hViewPositionMover->GetAbsOrigin(), angGoal );
  1018. }
  1019. else if ( m_hViewTargetAngles )
  1020. {
  1021. angCurrent = m_hViewTargetAngles->GetAbsAngles();
  1022. m_hViewTargetAngles->SetAbsOrigin( pPlayer->EyePosition() );
  1023. VectorAngles( m_hViewTarget->WorldSpaceCenter() - m_hViewTargetAngles->GetAbsOrigin(), angGoal );
  1024. }
  1025. else
  1026. {
  1027. angCurrent = pPlayer->EyeAngles();
  1028. VectorAngles( m_hViewTarget->WorldSpaceCenter() - pPlayer->EyePosition(), angGoal );
  1029. }
  1030. // Accelerate towards the target goal angles
  1031. float dx = AngleDiff( angGoal.x, angCurrent.x );
  1032. float dy = AngleDiff( angGoal.y, angCurrent.y );
  1033. float mod = 1.0 - ExponentialDecay( 0.5, 0.3, gpGlobals->frametime );
  1034. float dxmod = dx * mod;
  1035. float dymod = dy * mod;
  1036. angCurrent.x = AngleNormalize( angCurrent.x + dxmod );
  1037. angCurrent.y = AngleNormalize( angCurrent.y + dymod );
  1038. if ( m_hViewPositionMover )
  1039. {
  1040. m_hViewPositionMover->SetAbsAngles( angCurrent );
  1041. }
  1042. else if ( m_hViewTargetAngles )
  1043. {
  1044. m_hViewTargetAngles->SetAbsAngles( angCurrent );
  1045. pPlayer->SnapEyeAngles( angCurrent );
  1046. }
  1047. else
  1048. {
  1049. pPlayer->SnapEyeAngles( angCurrent );
  1050. }
  1051. SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink );
  1052. }
  1053. if ( m_hViewPosition.Get() )
  1054. {
  1055. if ( pPlayer->GetActiveWeapon() )
  1056. {
  1057. pPlayer->GetActiveWeapon()->Holster();
  1058. }
  1059. if ( !m_hViewPositionMover )
  1060. {
  1061. // Make an invisible info target entity for us to attach the view to,
  1062. // and move it to the desired view position.
  1063. m_hViewPositionMover = CreateEntityByName( "point_commentary_viewpoint" );
  1064. m_hViewPositionMover->SetAbsAngles( pPlayer->EyeAngles() );
  1065. pPlayer->SetViewEntity( m_hViewPositionMover );
  1066. }
  1067. // Blend to the target position over time.
  1068. float flCurTime = (gpGlobals->curtime - m_flStartTime);
  1069. float flBlendPerc = clamp( flCurTime * 0.5f, 0.f, 1.f );
  1070. // Figure out the current view position
  1071. Vector vecCurEye;
  1072. VectorLerp( pPlayer->EyePosition(), m_hViewPosition.Get()->GetAbsOrigin(), flBlendPerc, vecCurEye );
  1073. m_hViewPositionMover->SetAbsOrigin( vecCurEye );
  1074. SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink );
  1075. }
  1076. }
  1077. //-----------------------------------------------------------------------------
  1078. // Purpose:
  1079. //-----------------------------------------------------------------------------
  1080. void CPointCommentaryNode::UpdateViewPostThink( void )
  1081. {
  1082. CBasePlayer *pPlayer = GetCommentaryPlayer();
  1083. if ( !pPlayer )
  1084. return;
  1085. if ( m_hViewPosition.Get() && m_hViewPositionMover )
  1086. {
  1087. // Blend back to the player's position over time.
  1088. float flCurTime = (gpGlobals->curtime - m_flFinishedTime);
  1089. float flTimeToBlend = MIN( 2.0, m_flFinishedTime - m_flStartTime );
  1090. float flBlendPerc = 1.0f - clamp( flCurTime / flTimeToBlend, 0.f, 1.f );
  1091. //Msg("OUT: CurTime %.2f, BlendTime: %.2f, Blend: %.3f\n", flCurTime, flTimeToBlend, flBlendPerc );
  1092. // Only do this while we're still moving
  1093. if ( flBlendPerc > 0 )
  1094. {
  1095. // Figure out the current view position
  1096. Vector vecPlayerPos = pPlayer->EyePosition();
  1097. Vector vecToPosition = (m_vecFinishOrigin - vecPlayerPos);
  1098. Vector vecCurEye = pPlayer->EyePosition() + (vecToPosition * flBlendPerc);
  1099. m_hViewPositionMover->SetAbsOrigin( vecCurEye );
  1100. if ( m_hViewTarget )
  1101. {
  1102. Quaternion quatFinish;
  1103. Quaternion quatOriginal;
  1104. Quaternion quatCurrent;
  1105. AngleQuaternion( m_vecOriginalAngles, quatOriginal );
  1106. AngleQuaternion( m_vecFinishAngles, quatFinish );
  1107. QuaternionSlerp( quatFinish, quatOriginal, 1.0 - flBlendPerc, quatCurrent );
  1108. QAngle angCurrent;
  1109. QuaternionAngles( quatCurrent, angCurrent );
  1110. m_hViewPositionMover->SetAbsAngles( angCurrent );
  1111. }
  1112. SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink );
  1113. return;
  1114. }
  1115. pPlayer->SnapEyeAngles( m_hViewPositionMover->GetAbsAngles() );
  1116. }
  1117. // We're done
  1118. CleanupPostCommentary();
  1119. m_bPreventChangesWhileMoving = false;
  1120. }
  1121. //------------------------------------------------------------------------------
  1122. // Purpose:
  1123. //------------------------------------------------------------------------------
  1124. void CPointCommentaryNode::FinishCommentary( bool bBlendOut )
  1125. {
  1126. CBasePlayer *pPlayer = GetCommentaryPlayer();
  1127. if ( !pPlayer )
  1128. return;
  1129. // Fire off our postcommands
  1130. if ( m_iszPostCommands != NULL_STRING )
  1131. {
  1132. g_CommentarySystem.SetCommentaryConvarsChanging( true );
  1133. engine->ClientCommand( pPlayer->edict(), STRING(m_iszPostCommands) );
  1134. engine->ClientCommand( pPlayer->edict(), "commentary_cvarsnotchanging\n" );
  1135. }
  1136. // Stop the commentary
  1137. m_flFinishedTime = gpGlobals->curtime;
  1138. if ( bBlendOut && m_hViewPositionMover )
  1139. {
  1140. m_bActive = false;
  1141. m_flPlaybackRate = 1.0;
  1142. m_vecFinishOrigin = m_hViewPositionMover->GetAbsOrigin();
  1143. m_vecFinishAngles = m_hViewPositionMover->GetAbsAngles();
  1144. m_bPreventChangesWhileMoving = true;
  1145. // We've moved away from the player's position. Move back to it before ending
  1146. SetContextThink( &CPointCommentaryNode::UpdateViewPostThink, gpGlobals->curtime, s_pCommentaryUpdateViewThink );
  1147. return;
  1148. }
  1149. CleanupPostCommentary();
  1150. }
  1151. //-----------------------------------------------------------------------------
  1152. // Purpose:
  1153. //-----------------------------------------------------------------------------
  1154. void CPointCommentaryNode::CleanupPostCommentary( void )
  1155. {
  1156. CBasePlayer *pPlayer = GetCommentaryPlayer();
  1157. if ( !pPlayer )
  1158. return;
  1159. if ( ( m_hViewPositionMover || m_hViewTargetAngles ) && pPlayer->GetActiveWeapon() )
  1160. {
  1161. pPlayer->GetActiveWeapon()->Deploy();
  1162. }
  1163. if ( m_hViewTargetAngles && pPlayer->GetViewEntity() == m_hViewTargetAngles )
  1164. {
  1165. pPlayer->SetViewEntity( NULL );
  1166. }
  1167. UTIL_Remove( m_hViewTargetAngles );
  1168. if ( m_hViewPositionMover && pPlayer->GetViewEntity() == m_hViewPositionMover )
  1169. {
  1170. pPlayer->SetViewEntity( NULL );
  1171. }
  1172. UTIL_Remove( m_hViewPositionMover );
  1173. m_bActive = false;
  1174. m_flPlaybackRate = 1.0;
  1175. m_bUnstoppable = false;
  1176. m_flFinishedTime = 0;
  1177. SetContextThink( NULL, 0, s_pCommentaryUpdateViewThink );
  1178. // Clear out any events waiting on our start commentary
  1179. g_EventQueue.CancelEvents( this );
  1180. m_pOnCommentaryStopped.FireOutput( this, this );
  1181. g_CommentarySystem.SetActiveNode( NULL );
  1182. }
  1183. //-----------------------------------------------------------------------------
  1184. // Purpose:
  1185. //-----------------------------------------------------------------------------
  1186. void CPointCommentaryNode::InputStartCommentary( inputdata_t &inputdata )
  1187. {
  1188. if ( !m_bActive )
  1189. {
  1190. if ( g_CommentarySystem.GetActiveNode() )
  1191. {
  1192. g_CommentarySystem.GetActiveNode()->StopPlaying();
  1193. }
  1194. PlayerActivated();
  1195. }
  1196. }
  1197. //-----------------------------------------------------------------------------
  1198. // Purpose:
  1199. //-----------------------------------------------------------------------------
  1200. void CPointCommentaryNode::InputStartUnstoppableCommentary( inputdata_t &inputdata )
  1201. {
  1202. if ( !m_bActive )
  1203. {
  1204. m_bUnstoppable = true;
  1205. InputStartCommentary( inputdata );
  1206. }
  1207. }
  1208. //-----------------------------------------------------------------------------
  1209. // Purpose:
  1210. //-----------------------------------------------------------------------------
  1211. void CPointCommentaryNode::InputEnable( inputdata_t &inputdata )
  1212. {
  1213. SetDisabled( false );
  1214. }
  1215. //-----------------------------------------------------------------------------
  1216. // Purpose:
  1217. //-----------------------------------------------------------------------------
  1218. void CPointCommentaryNode::InputDisable( inputdata_t &inputdata )
  1219. {
  1220. SetDisabled( true );
  1221. }
  1222. //-----------------------------------------------------------------------------
  1223. // Purpose:
  1224. //-----------------------------------------------------------------------------
  1225. void CPointCommentaryNode::SetDisabled( bool bDisabled )
  1226. {
  1227. m_bDisabled = bDisabled;
  1228. if ( m_bDisabled )
  1229. {
  1230. AddEffects( EF_NODRAW );
  1231. }
  1232. else
  1233. {
  1234. RemoveEffects( EF_NODRAW );
  1235. }
  1236. }
  1237. //-----------------------------------------------------------------------------
  1238. // Purpose Force our lighting landmark to be transmitted
  1239. //-----------------------------------------------------------------------------
  1240. int CPointCommentaryNode::UpdateTransmitState( void )
  1241. {
  1242. return SetTransmitState( FL_EDICT_ALWAYS );
  1243. }
  1244. //-----------------------------------------------------------------------------
  1245. // Purpose:
  1246. //-----------------------------------------------------------------------------
  1247. void CPointCommentaryNode::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
  1248. {
  1249. // Are we already marked for transmission?
  1250. if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
  1251. return;
  1252. BaseClass::SetTransmit( pInfo, bAlways );
  1253. // Force our camera view position entity to be sent
  1254. if ( m_hViewTarget )
  1255. {
  1256. m_hViewTarget->SetTransmit( pInfo, bAlways );
  1257. }
  1258. if ( m_hViewTargetAngles )
  1259. {
  1260. m_hViewTargetAngles->SetTransmit( pInfo, bAlways );
  1261. }
  1262. if ( m_hViewPosition.Get() )
  1263. {
  1264. m_hViewPosition.Get()->SetTransmit( pInfo, bAlways );
  1265. }
  1266. if ( m_hViewPositionMover )
  1267. {
  1268. m_hViewPositionMover->SetTransmit( pInfo, bAlways );
  1269. }
  1270. }
  1271. //-----------------------------------------------------------------------------
  1272. // Purpose:
  1273. //-----------------------------------------------------------------------------
  1274. bool CPointCommentaryNode::PreventsMovement( void )
  1275. {
  1276. // If we're moving the player's view at all, prevent movement
  1277. if ( m_hViewPosition.Get() )
  1278. return true;
  1279. return m_bPreventMovement;
  1280. }
  1281. //-----------------------------------------------------------------------------
  1282. // COMMENTARY SAVE / RESTORE
  1283. //-----------------------------------------------------------------------------
  1284. static short COMMENTARY_SAVE_RESTORE_VERSION = 2;
  1285. class CCommentary_SaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler
  1286. {
  1287. public:
  1288. const char *GetBlockName()
  1289. {
  1290. return "Commentary";
  1291. }
  1292. //---------------------------------
  1293. void Save( ISave *pSave )
  1294. {
  1295. pSave->WriteBool( &g_bInCommentaryMode );
  1296. if ( IsInCommentaryMode() )
  1297. {
  1298. pSave->WriteAll( &g_CommentarySystem, g_CommentarySystem.GetDataDescMap() );
  1299. pSave->WriteInt( &CAI_BaseNPC::m_nDebugBits );
  1300. }
  1301. }
  1302. //---------------------------------
  1303. void WriteSaveHeaders( ISave *pSave )
  1304. {
  1305. pSave->WriteShort( &COMMENTARY_SAVE_RESTORE_VERSION );
  1306. }
  1307. //---------------------------------
  1308. void ReadRestoreHeaders( IRestore *pRestore )
  1309. {
  1310. // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so.
  1311. short version;
  1312. pRestore->ReadShort( &version );
  1313. m_fDoLoad = ( version == COMMENTARY_SAVE_RESTORE_VERSION );
  1314. }
  1315. //---------------------------------
  1316. void Restore( IRestore *pRestore, bool createPlayers )
  1317. {
  1318. if ( m_fDoLoad )
  1319. {
  1320. pRestore->ReadBool( &g_bInCommentaryMode );
  1321. if ( g_bInCommentaryMode )
  1322. {
  1323. pRestore->ReadAll( &g_CommentarySystem, g_CommentarySystem.GetDataDescMap() );
  1324. CAI_BaseNPC::m_nDebugBits = pRestore->ReadInt();
  1325. }
  1326. // Force the commentary convar to match the saved game state
  1327. commentary.SetValue( g_bInCommentaryMode );
  1328. }
  1329. }
  1330. private:
  1331. bool m_fDoLoad;
  1332. };
  1333. //-----------------------------------------------------------------------------
  1334. CCommentary_SaveRestoreBlockHandler g_Commentary_SaveRestoreBlockHandler;
  1335. //-------------------------------------
  1336. ISaveRestoreBlockHandler *GetCommentarySaveRestoreBlockHandler()
  1337. {
  1338. return &g_Commentary_SaveRestoreBlockHandler;
  1339. }
  1340. //-----------------------------------------------------------------------------
  1341. // Purpose: Commentary specific logic_auto replacement.
  1342. // Fires outputs based upon how commentary mode has been activated.
  1343. //-----------------------------------------------------------------------------
  1344. class CCommentaryAuto : public CBaseEntity
  1345. {
  1346. DECLARE_CLASS( CCommentaryAuto, CBaseEntity );
  1347. public:
  1348. DECLARE_DATADESC();
  1349. void Spawn(void);
  1350. void Think(void);
  1351. void InputMultiplayerSpawned( inputdata_t &inputdata );
  1352. private:
  1353. // fired if commentary started due to new map
  1354. COutputEvent m_OnCommentaryNewGame;
  1355. // fired if commentary was turned on in the middle of a map
  1356. COutputEvent m_OnCommentaryMidGame;
  1357. // fired when the player spawns in a multiplayer game
  1358. COutputEvent m_OnCommentaryMultiplayerSpawn;
  1359. };
  1360. LINK_ENTITY_TO_CLASS(commentary_auto, CCommentaryAuto);
  1361. BEGIN_DATADESC( CCommentaryAuto )
  1362. // Inputs
  1363. DEFINE_INPUTFUNC( FIELD_VOID, "MultiplayerSpawned", InputMultiplayerSpawned ),
  1364. // Outputs
  1365. DEFINE_OUTPUT(m_OnCommentaryNewGame, "OnCommentaryNewGame"),
  1366. DEFINE_OUTPUT(m_OnCommentaryMidGame, "OnCommentaryMidGame"),
  1367. DEFINE_OUTPUT(m_OnCommentaryMultiplayerSpawn, "OnCommentaryMultiplayerSpawn"),
  1368. END_DATADESC()
  1369. //------------------------------------------------------------------------------
  1370. // Purpose : Fire my outputs here if I fire on map reload
  1371. //------------------------------------------------------------------------------
  1372. void CCommentaryAuto::Spawn(void)
  1373. {
  1374. BaseClass::Spawn();
  1375. SetNextThink( gpGlobals->curtime + 0.1 );
  1376. }
  1377. //-----------------------------------------------------------------------------
  1378. // Purpose:
  1379. //-----------------------------------------------------------------------------
  1380. void CCommentaryAuto::Think(void)
  1381. {
  1382. if ( g_CommentarySystem.CommentaryWasEnabledMidGame() )
  1383. {
  1384. m_OnCommentaryMidGame.FireOutput(NULL, this);
  1385. }
  1386. else
  1387. {
  1388. m_OnCommentaryNewGame.FireOutput(NULL, this);
  1389. }
  1390. }
  1391. //-----------------------------------------------------------------------------
  1392. // Purpose:
  1393. //-----------------------------------------------------------------------------
  1394. void CCommentaryAuto::InputMultiplayerSpawned( inputdata_t &inputdata )
  1395. {
  1396. m_OnCommentaryMultiplayerSpawn.FireOutput( NULL, this );
  1397. }
  1398. #else
  1399. bool IsInCommentaryMode( void )
  1400. {
  1401. return false;
  1402. }
  1403. #endif