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.

585 lines
19 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "c_baseentity.h"
  8. #include "hud.h"
  9. #include "hudelement.h"
  10. #include "clientmode.h"
  11. #include <vgui_controls/Panel.h>
  12. #include <vgui/ISurface.h>
  13. #include <vgui/ILocalize.h>
  14. #include <vgui/IScheme.h>
  15. #include <vgui_controls/AnimationController.h>
  16. #include "materialsystem/imaterialsystemhardwareconfig.h"
  17. #include "soundenvelope.h"
  18. #include "convar.h"
  19. #include "hud_closecaption.h"
  20. #include "in_buttons.h"
  21. // memdbgon must be the last include file in a .cpp file!!!
  22. #include "tier0/memdbgon.h"
  23. #define MAX_SPEAKER_NAME 256
  24. #define MAX_COUNT_STRING 64
  25. extern ConVar english;
  26. extern ConVar closecaption;
  27. class C_PointCommentaryNode;
  28. CUtlVector< CHandle<C_PointCommentaryNode> > g_CommentaryNodes;
  29. bool IsInCommentaryMode( void )
  30. {
  31. return (g_CommentaryNodes.Count() > 0);
  32. }
  33. static bool g_bTracingVsCommentaryNodes = false;
  34. //-----------------------------------------------------------------------------
  35. // Purpose:
  36. //-----------------------------------------------------------------------------
  37. class CHudCommentary : public CHudElement, public vgui::Panel
  38. {
  39. DECLARE_CLASS_SIMPLE( CHudCommentary, vgui::Panel );
  40. public:
  41. CHudCommentary( const char *name );
  42. virtual void Init( void );
  43. virtual void VidInit( void );
  44. virtual void LevelInit( void ) { g_CommentaryNodes.Purge(); }
  45. virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
  46. void StartCommentary( C_PointCommentaryNode *pNode, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime );
  47. void StopCommentary( void );
  48. bool IsTheActiveNode( C_PointCommentaryNode *pNode ) { return (pNode == m_hActiveNode); }
  49. // vgui overrides
  50. virtual void Paint( void );
  51. virtual bool ShouldDraw( void );
  52. private:
  53. CHandle<C_PointCommentaryNode> m_hActiveNode;
  54. bool m_bShouldPaint;
  55. float m_flStartTime;
  56. float m_flEndTime;
  57. wchar_t m_szSpeakers[MAX_SPEAKER_NAME];
  58. wchar_t m_szCount[MAX_COUNT_STRING];
  59. CMaterialReference m_matIcon;
  60. bool m_bHiding;
  61. // Painting
  62. CPanelAnimationVarAliasType( int, m_iBarX, "bar_xpos", "8", "proportional_int" );
  63. CPanelAnimationVarAliasType( int, m_iBarY, "bar_ypos", "8", "proportional_int" );
  64. CPanelAnimationVarAliasType( int, m_iBarTall, "bar_height", "16", "proportional_int" );
  65. CPanelAnimationVarAliasType( int, m_iBarWide, "bar_width", "16", "proportional_int" );
  66. CPanelAnimationVarAliasType( int, m_iSpeakersX, "speaker_xpos", "8", "proportional_int" );
  67. CPanelAnimationVarAliasType( int, m_iSpeakersY, "speaker_ypos", "8", "proportional_int" );
  68. CPanelAnimationVarAliasType( int, m_iCountXFR, "count_xpos_from_right", "8", "proportional_int" );
  69. CPanelAnimationVarAliasType( int, m_iCountY, "count_ypos", "8", "proportional_int" );
  70. CPanelAnimationVarAliasType( int, m_iIconX, "icon_xpos", "8", "proportional_int" );
  71. CPanelAnimationVarAliasType( int, m_iIconY, "icon_ypos", "8", "proportional_int" );
  72. CPanelAnimationVarAliasType( int, m_iIconWide, "icon_width", "8", "proportional_int" );
  73. CPanelAnimationVarAliasType( int, m_iIconTall, "icon_height", "8", "proportional_int" );
  74. CPanelAnimationVarAliasType( int, m_nIconTextureId, "icon_texture", "vgui/hud/icon_commentary", "textureid" );
  75. CPanelAnimationVar( bool, m_bUseScriptBGColor, "use_script_bgcolor", "0" );
  76. CPanelAnimationVar( Color, m_BackgroundColor, "BackgroundColor", "0 0 0 0" );
  77. CPanelAnimationVar( Color, m_BGOverrideColor, "BackgroundOverrideColor", "Panel.BgColor" );
  78. };
  79. //-----------------------------------------------------------------------------
  80. // Purpose:
  81. //-----------------------------------------------------------------------------
  82. class C_PointCommentaryNode : public C_BaseAnimating
  83. {
  84. DECLARE_CLASS( C_PointCommentaryNode, C_BaseAnimating );
  85. public:
  86. DECLARE_CLIENTCLASS();
  87. DECLARE_DATADESC();
  88. virtual void OnPreDataChanged( DataUpdateType_t type );
  89. virtual void OnDataChanged( DataUpdateType_t type );
  90. void OnRestore( void )
  91. {
  92. BaseClass::OnRestore();
  93. if ( m_bActive )
  94. {
  95. StopLoopingSounds();
  96. m_bRestartAfterRestore = true;
  97. }
  98. AddAndLockCommentaryHudGroup();
  99. }
  100. //-----------------------------------------------------------------------------
  101. // Purpose:
  102. //-----------------------------------------------------------------------------
  103. virtual void SetDormant( bool bDormant )
  104. {
  105. if ( !IsDormant() && bDormant )
  106. {
  107. RemoveAndUnlockCommentaryHudGroup();
  108. }
  109. BaseClass::SetDormant( bDormant );
  110. }
  111. //-----------------------------------------------------------------------------
  112. // Cleanup
  113. //-----------------------------------------------------------------------------
  114. void UpdateOnRemove( void )
  115. {
  116. RemoveAndUnlockCommentaryHudGroup();
  117. StopLoopingSounds();
  118. BaseClass::UpdateOnRemove();
  119. }
  120. void StopLoopingSounds( void );
  121. virtual bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace );
  122. void AddAndLockCommentaryHudGroup( void )
  123. {
  124. if ( !g_CommentaryNodes.Count() )
  125. {
  126. int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "commentary" );
  127. gHUD.LockRenderGroup( iRenderGroup );
  128. }
  129. if ( g_CommentaryNodes.Find(this) == g_CommentaryNodes.InvalidIndex() )
  130. {
  131. g_CommentaryNodes.AddToTail( this );
  132. }
  133. }
  134. void RemoveAndUnlockCommentaryHudGroup( void )
  135. {
  136. g_CommentaryNodes.FindAndRemove( this );
  137. if ( !g_CommentaryNodes.Count() )
  138. {
  139. int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "commentary" );
  140. gHUD.UnlockRenderGroup( iRenderGroup );
  141. }
  142. }
  143. public:
  144. // Data received from the server
  145. bool m_bActive;
  146. bool m_bWasActive;
  147. float m_flStartTime;
  148. char m_iszCommentaryFile[MAX_PATH];
  149. char m_iszCommentaryFileNoHDR[MAX_PATH];
  150. char m_iszSpeakers[MAX_SPEAKER_NAME];
  151. int m_iNodeNumber;
  152. int m_iNodeNumberMax;
  153. CSoundPatch *m_sndCommentary;
  154. EHANDLE m_hViewPosition;
  155. bool m_bRestartAfterRestore;
  156. };
  157. IMPLEMENT_CLIENTCLASS_DT(C_PointCommentaryNode, DT_PointCommentaryNode, CPointCommentaryNode)
  158. RecvPropBool( RECVINFO( m_bActive ) ),
  159. RecvPropTime( RECVINFO( m_flStartTime ) ),
  160. RecvPropString( RECVINFO(m_iszCommentaryFile) ),
  161. RecvPropString( RECVINFO(m_iszCommentaryFileNoHDR) ),
  162. RecvPropString( RECVINFO(m_iszSpeakers) ),
  163. RecvPropInt( RECVINFO( m_iNodeNumber ) ),
  164. RecvPropInt( RECVINFO( m_iNodeNumberMax ) ),
  165. RecvPropEHandle( RECVINFO(m_hViewPosition) ),
  166. END_RECV_TABLE()
  167. BEGIN_DATADESC( C_PointCommentaryNode )
  168. DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
  169. DEFINE_FIELD( m_bWasActive, FIELD_BOOLEAN ),
  170. DEFINE_SOUNDPATCH( m_sndCommentary ),
  171. END_DATADESC()
  172. //-----------------------------------------------------------------------------
  173. // Purpose:
  174. //-----------------------------------------------------------------------------
  175. void C_PointCommentaryNode::OnPreDataChanged( DataUpdateType_t updateType )
  176. {
  177. BaseClass::OnPreDataChanged( updateType );
  178. m_bWasActive = m_bActive;
  179. }
  180. //-----------------------------------------------------------------------------
  181. // Purpose:
  182. //-----------------------------------------------------------------------------
  183. void C_PointCommentaryNode::OnDataChanged( DataUpdateType_t updateType )
  184. {
  185. BaseClass::OnDataChanged( updateType );
  186. if ( updateType == DATA_UPDATE_CREATED )
  187. {
  188. AddAndLockCommentaryHudGroup();
  189. }
  190. if ( m_bWasActive == m_bActive && !m_bRestartAfterRestore )
  191. return;
  192. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
  193. if ( m_bActive && pPlayer )
  194. {
  195. // Use the HDR / Non-HDR version based on whether we're running HDR or not
  196. char *pszCommentaryFile;
  197. if ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_NONE && m_iszCommentaryFileNoHDR && m_iszCommentaryFileNoHDR[0] )
  198. {
  199. pszCommentaryFile = m_iszCommentaryFileNoHDR;
  200. }
  201. else
  202. {
  203. pszCommentaryFile = m_iszCommentaryFile;
  204. }
  205. if ( !pszCommentaryFile || !pszCommentaryFile[0] )
  206. {
  207. engine->ServerCmd( "commentary_finishnode\n" );
  208. return;
  209. }
  210. EmitSound_t es;
  211. es.m_nChannel = CHAN_STATIC;
  212. es.m_pSoundName = pszCommentaryFile;
  213. es.m_SoundLevel = SNDLVL_GUNFIRE;
  214. es.m_nFlags = SND_SHOULDPAUSE;
  215. CBaseEntity *pSoundEntity;
  216. if ( m_hViewPosition )
  217. {
  218. pSoundEntity = m_hViewPosition;
  219. }
  220. else if ( render->GetViewEntity() )
  221. {
  222. pSoundEntity = cl_entitylist->GetEnt( render->GetViewEntity() );
  223. es.m_SoundLevel = SNDLVL_NONE;
  224. }
  225. else
  226. {
  227. pSoundEntity = pPlayer;
  228. }
  229. CSingleUserRecipientFilter filter( pPlayer );
  230. m_sndCommentary = (CSoundEnvelopeController::GetController()).SoundCreate( filter, pSoundEntity->entindex(), es );
  231. if ( m_sndCommentary )
  232. {
  233. (CSoundEnvelopeController::GetController()).SoundSetCloseCaptionDuration( m_sndCommentary, -1 );
  234. (CSoundEnvelopeController::GetController()).Play( m_sndCommentary, 1.0f, 100, m_flStartTime );
  235. }
  236. // Get the duration so we know when it finishes
  237. float flDuration = enginesound->GetSoundDuration( STRING( CSoundEnvelopeController::GetController().SoundGetName( m_sndCommentary ) ) ) ;
  238. CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption );
  239. if ( pHudCloseCaption )
  240. {
  241. // This is where we play the commentary close caption (and lock the other captions out).
  242. // Also, if close captions are off we force a caption in non-English
  243. if ( closecaption.GetBool() || ( !closecaption.GetBool() && !english.GetBool() ) )
  244. {
  245. // Clear the close caption element in preparation
  246. pHudCloseCaption->Reset();
  247. // Process the commentary caption
  248. pHudCloseCaption->ProcessCaptionDirect( pszCommentaryFile, flDuration );
  249. // Find the close caption hud element & lock it
  250. pHudCloseCaption->Lock();
  251. }
  252. }
  253. // Tell the HUD element
  254. CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary );
  255. pHudCommentary->StartCommentary( this, m_iszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration );
  256. }
  257. else if ( m_bWasActive )
  258. {
  259. StopLoopingSounds();
  260. CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary );
  261. if ( pHudCommentary->IsTheActiveNode(this) )
  262. {
  263. pHudCommentary->StopCommentary();
  264. }
  265. }
  266. m_bRestartAfterRestore = false;
  267. }
  268. //-----------------------------------------------------------------------------
  269. // Purpose: Shut down the commentary
  270. //-----------------------------------------------------------------------------
  271. void C_PointCommentaryNode::StopLoopingSounds( void )
  272. {
  273. if ( m_sndCommentary != NULL )
  274. {
  275. (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndCommentary );
  276. m_sndCommentary = NULL;
  277. }
  278. }
  279. //-----------------------------------------------------------------------------
  280. // Purpose: No client side trace collisions
  281. //-----------------------------------------------------------------------------
  282. bool C_PointCommentaryNode::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
  283. {
  284. if ( !g_bTracingVsCommentaryNodes )
  285. return false;
  286. return BaseClass::TestCollision( ray, mask, trace );
  287. }
  288. //-----------------------------------------------------------------------------
  289. // Purpose:
  290. //-----------------------------------------------------------------------------
  291. bool IsNodeUnderCrosshair( C_BasePlayer *pPlayer )
  292. {
  293. // See if the player's looking at a commentary node
  294. trace_t tr;
  295. Vector vecSrc = pPlayer->EyePosition();
  296. Vector vecForward;
  297. AngleVectors( pPlayer->EyeAngles(), &vecForward );
  298. g_bTracingVsCommentaryNodes = true;
  299. UTIL_TraceLine( vecSrc, vecSrc + vecForward * MAX_TRACE_LENGTH, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
  300. g_bTracingVsCommentaryNodes = false;
  301. if ( !tr.m_pEnt )
  302. return false;
  303. return dynamic_cast<C_PointCommentaryNode*>(tr.m_pEnt);
  304. }
  305. //===================================================================================================================
  306. // COMMENTARY HUD ELEMENT
  307. //===================================================================================================================
  308. DECLARE_HUDELEMENT( CHudCommentary );
  309. //-----------------------------------------------------------------------------
  310. // Purpose:
  311. //-----------------------------------------------------------------------------
  312. CHudCommentary::CHudCommentary( const char *name ) : vgui::Panel( NULL, "HudCommentary" ), CHudElement( name )
  313. {
  314. vgui::Panel *pParent = g_pClientMode->GetViewport();
  315. SetParent( pParent );
  316. SetPaintBorderEnabled( false );
  317. SetHiddenBits( HIDEHUD_PLAYERDEAD );
  318. m_hActiveNode = NULL;
  319. m_bShouldPaint = true;
  320. }
  321. void CHudCommentary::ApplySchemeSettings( vgui::IScheme *pScheme )
  322. {
  323. BaseClass::ApplySchemeSettings( pScheme );
  324. if ( m_bUseScriptBGColor )
  325. {
  326. SetBgColor( m_BGOverrideColor );
  327. }
  328. }
  329. //-----------------------------------------------------------------------------
  330. // Purpose:
  331. //-----------------------------------------------------------------------------
  332. void CHudCommentary::Paint()
  333. {
  334. float flDuration = (m_flEndTime - m_flStartTime);
  335. float flPercentage = clamp( ( gpGlobals->curtime - m_flStartTime ) / flDuration, 0.f, 1.f );
  336. if ( !m_hActiveNode )
  337. {
  338. if ( !m_bHiding )
  339. {
  340. m_bHiding = true;
  341. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "HideCommentary" );
  342. CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption );
  343. if ( pHudCloseCaption )
  344. {
  345. pHudCloseCaption->Reset();
  346. }
  347. }
  348. }
  349. else
  350. {
  351. // Detect the end of the commentary
  352. if ( flPercentage >= 1 && m_hActiveNode )
  353. {
  354. m_hActiveNode = NULL;
  355. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "HideCommentary" );
  356. engine->ServerCmd( "commentary_finishnode\n" );
  357. }
  358. }
  359. if ( !m_bShouldPaint )
  360. return;
  361. int x, y, wide, tall;
  362. GetBounds( x, y, wide, tall );
  363. int xOffset = m_iBarX;
  364. int yOffset = m_iBarY;
  365. // Find our fade based on our time shown
  366. Color clr = Color( 255, 170, 0, GetAlpha() );
  367. // Draw the progress bar
  368. vgui::surface()->DrawSetColor( clr );
  369. vgui::surface()->DrawOutlinedRect( xOffset, yOffset, xOffset+m_iBarWide, yOffset+m_iBarTall );
  370. vgui::surface()->DrawSetColor( clr );
  371. vgui::surface()->DrawFilledRect( xOffset+2, yOffset+2, xOffset+(int)(flPercentage*m_iBarWide)-2, yOffset+m_iBarTall-2 );
  372. // Draw the speaker names
  373. // Get our scheme and font information
  374. vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" );
  375. vgui::HFont hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" );
  376. if ( !hFont )
  377. {
  378. hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" );
  379. }
  380. vgui::surface()->DrawSetTextFont( hFont );
  381. vgui::surface()->DrawSetTextColor( clr );
  382. vgui::surface()->DrawSetTextPos( m_iSpeakersX, m_iSpeakersY );
  383. vgui::surface()->DrawPrintText( m_szSpeakers, wcslen(m_szSpeakers) );
  384. if ( COMMENTARY_BUTTONS & IN_ATTACK )
  385. {
  386. int iY = m_iBarY + m_iBarTall + YRES(4);
  387. wchar_t wzFinal[512] = L"";
  388. wchar_t *pszText = g_pVGuiLocalize->Find( "#Commentary_PrimaryAttack" );
  389. if ( pszText )
  390. {
  391. UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) );
  392. vgui::surface()->DrawSetTextPos( m_iSpeakersX, iY );
  393. vgui::surface()->DrawPrintText( wzFinal, wcslen(wzFinal) );
  394. }
  395. pszText = g_pVGuiLocalize->Find( "#Commentary_SecondaryAttack" );
  396. if ( pszText )
  397. {
  398. int w, h;
  399. UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) );
  400. vgui::surface()->GetTextSize( hFont, wzFinal, w, h );
  401. vgui::surface()->DrawSetTextPos( m_iBarX + m_iBarWide - w, iY );
  402. vgui::surface()->DrawPrintText( wzFinal, wcslen(wzFinal) );
  403. }
  404. }
  405. // Draw the commentary count
  406. // Determine our text size, and move that far in from the right hand size (plus the offset)
  407. int iCountWide, iCountTall;
  408. vgui::surface()->GetTextSize( hFont, m_szCount, iCountWide, iCountTall );
  409. vgui::surface()->DrawSetTextPos( wide - m_iCountXFR - iCountWide, m_iCountY );
  410. vgui::surface()->DrawPrintText( m_szCount, wcslen(m_szCount) );
  411. // Draw the icon
  412. vgui::surface()->DrawSetColor( Color(255,170,0,GetAlpha()) );
  413. vgui::surface()->DrawSetTexture(m_nIconTextureId);
  414. vgui::surface()->DrawTexturedRect( m_iIconX, m_iIconY, m_iIconWide, m_iIconTall );
  415. }
  416. //-----------------------------------------------------------------------------
  417. // Purpose:
  418. //-----------------------------------------------------------------------------
  419. bool CHudCommentary::ShouldDraw()
  420. {
  421. return ( m_hActiveNode || GetAlpha() > 0 );
  422. }
  423. //-----------------------------------------------------------------------------
  424. // Purpose:
  425. //-----------------------------------------------------------------------------
  426. void CHudCommentary::Init( void )
  427. {
  428. m_matIcon.Init( "vgui/hud/icon_commentary", TEXTURE_GROUP_VGUI );
  429. }
  430. //-----------------------------------------------------------------------------
  431. // Purpose:
  432. //-----------------------------------------------------------------------------
  433. void CHudCommentary::VidInit( void )
  434. {
  435. SetAlpha(0);
  436. StopCommentary();
  437. }
  438. //-----------------------------------------------------------------------------
  439. // Purpose:
  440. //-----------------------------------------------------------------------------
  441. void CHudCommentary::StartCommentary( C_PointCommentaryNode *pNode, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime )
  442. {
  443. if ( (flEndTime - flStartTime) <= 0 )
  444. return;
  445. m_hActiveNode = pNode;
  446. m_flStartTime = flStartTime;
  447. m_flEndTime = flEndTime;
  448. m_bHiding = false;
  449. g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof(m_szSpeakers) );
  450. // Don't draw the element itself if closecaptions are on (and captions are always on in non-english mode)
  451. ConVarRef pCVar( "closecaption" );
  452. if ( pCVar.IsValid() )
  453. {
  454. m_bShouldPaint = ( !pCVar.GetBool() && english.GetBool() );
  455. }
  456. else
  457. {
  458. m_bShouldPaint = true;
  459. }
  460. SetPaintBackgroundEnabled( m_bShouldPaint );
  461. char sz[MAX_COUNT_STRING];
  462. Q_snprintf( sz, sizeof(sz), "%d \\ %d", iNode, iNodeMax );
  463. g_pVGuiLocalize->ConvertANSIToUnicode( sz, m_szCount, sizeof(m_szCount) );
  464. // If the commentary just started, play the commentary fade in.
  465. if ( fabs(flStartTime - gpGlobals->curtime) < 1.0 )
  466. {
  467. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "ShowCommentary" );
  468. }
  469. else
  470. {
  471. // We're reloading a savegame that has an active commentary going in it. Don't fade in.
  472. SetAlpha( 255 );
  473. }
  474. }
  475. //-----------------------------------------------------------------------------
  476. // Purpose:
  477. //-----------------------------------------------------------------------------
  478. void CHudCommentary::StopCommentary( void )
  479. {
  480. m_hActiveNode = NULL;
  481. }
  482. //-----------------------------------------------------------------------------
  483. // Purpose:
  484. //-----------------------------------------------------------------------------
  485. bool CommentaryModeShouldSwallowInput( C_BasePlayer *pPlayer )
  486. {
  487. if ( !IsInCommentaryMode() )
  488. return false;
  489. if ( pPlayer->m_nButtons & COMMENTARY_BUTTONS )
  490. {
  491. // Always steal the secondary attack
  492. if ( pPlayer->m_nButtons & IN_ATTACK2 )
  493. return true;
  494. // See if there's any nodes ahead of us.
  495. if ( IsNodeUnderCrosshair( pPlayer ) )
  496. return true;
  497. }
  498. return false;
  499. }