Counter Strike : Global Offensive Source Code
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.

1908 lines
57 KiB

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