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.

2856 lines
78 KiB

  1. //===== Copyright Valve Corporation, All rights reserved. ======//
  2. //
  3. // Radial, context-sensitive menu for co-op communication
  4. //
  5. //================================================================//
  6. #include "cbase.h"
  7. #include <string.h>
  8. #include <stdio.h>
  9. #include <vgui_controls/ImagePanel.h>
  10. #include "voice_status.h"
  11. #include "c_playerresource.h"
  12. #include "cliententitylist.h"
  13. #include "c_baseplayer.h"
  14. #include "materialsystem/imesh.h"
  15. #include "view.h"
  16. #include "materialsystem/imaterial.h"
  17. #include "tier0/dbg.h"
  18. #include "cdll_int.h"
  19. #include "menu.h" // for chudmenu defs
  20. #include "keyvalues.h"
  21. #include <filesystem.h>
  22. #include "c_team.h"
  23. #include "vgui/isurface.h"
  24. #include "iclientmode.h"
  25. #include "c_portal_player.h"
  26. #include "hud_locator_target.h"
  27. #include "c_user_message_register.h"
  28. #include "portal_placement.h"
  29. #include "glow_outline_effect.h"
  30. #include "soundemittersystem/isoundemittersystembase.h"
  31. #include "c_prop_portal.h"
  32. #include "c_trigger_tractorbeam.h"
  33. #include "c_projectedwallentity.h"
  34. #include "portal_mp_gamerules.h"
  35. #include "vgui/cursor.h"
  36. #include "fmtstr.h"
  37. #include "vgui_int.h"
  38. #include "vgui/IVgui.h"
  39. #include <game/client/iviewport.h>
  40. #include "radialmenu.h"
  41. #include "radialmenu_taunt.h"
  42. #include "radialbutton.h"
  43. #include "cegclientwrapper.h"
  44. // memdbgon must be the last include file in a .cpp file!!!
  45. #include "tier0/memdbgon.h"
  46. extern void AddLocator( C_BaseEntity *pTarget, const Vector &vPosition, const Vector &vNormal, int nPlayerIndex, const char *caption, float fDisplayTime );
  47. ConVar cl_fastradial( "cl_fastradial", "1", FCVAR_DEVELOPMENTONLY, "If 1, releasing the button on a radial menu executes the highlighted button" );
  48. ConVar RadialMenuDebug( "cl_rosette_debug", "0" );
  49. ConVar cl_rosette_line_inner_radius( "cl_rosette_line_inner_radius", "25" );
  50. ConVar cl_rosette_line_outer_radius( "cl_rosette_line_outer_radius", "45" );
  51. ConVar cl_rosette_gamepad_lockin_time( "cl_rosette_gamepad_lockin_time", "0.2" );
  52. ConVar cl_rosette_gamepad_expand_time( "cl_rosette_gamepad_expand_time", "0.5" );
  53. #if defined( PORTAL2_PUZZLEMAKER )
  54. extern ConVar sv_record_playtest;
  55. #endif // PORTAL2_PUZZLEMAKER
  56. #define PING_DELAY_BASE 0.05f
  57. #define PING_DELAY_INCREMENT 0.05f
  58. #define PING_SOUND_NAME "GameUI.UiCoopHudClick"
  59. #define PING_SOUND_NAME_LOW "GameUI.UiCoopHudClickLow"
  60. #define PING_SOUND_NAME_HIGH "GameUI.UiCoopHudClickHigh"
  61. #define RADIAL_MENU_POINTER_TEXTURE "vgui/hud/icon_arrow_right"
  62. void FlushClientMenus( void );
  63. void CloseRadialMenuCommand( RadialMenuTypes_t menuType, bool bForceClose = false );
  64. static ClientMenuManager TheClientMenuManager;
  65. static ClientMenuManagerPlaytest TheClientMenuManagerPlaytest;
  66. //--------------------------------------------------------------------------------------------------------
  67. static char s_radialMenuName[ MAX_SPLITSCREEN_PLAYERS ][ 64 ];
  68. static bool s_mouseMenuKeyHeld[ MAX_SPLITSCREEN_PLAYERS ];
  69. // Precache our glow effects
  70. PRECACHE_REGISTER_BEGIN( GLOBAL, PrecacheRadialMenu )
  71. PRECACHE( MATERIAL, "dev/glow_blur_x" )
  72. PRECACHE( MATERIAL, "dev/glow_blur_y" )
  73. PRECACHE( MATERIAL, "dev/glow_color" )
  74. PRECACHE( MATERIAL, "dev/glow_downsample" )
  75. PRECACHE( MATERIAL, "dev/halo_add_to_screen" )
  76. PRECACHE( MATERIAL, RADIAL_MENU_POINTER_TEXTURE )
  77. PRECACHE_REGISTER_END()
  78. #define GLOW_OUTLINE_COLOR Vector( 0.25f, 0.62f, 1.0f )
  79. #define GLOW_OUTLINE_ALPHA 0.75f
  80. struct SignifierInfo_t
  81. {
  82. EHANDLE hTarget;
  83. Vector vPos;
  84. Vector vNormal;
  85. int nPlayerIndex;
  86. char szCaption[ 32 ];
  87. float fDisplayTime;
  88. };
  89. CUtlVector< SignifierInfo_t > g_SignifierQueue[ MAX_SPLITSCREEN_PLAYERS ];
  90. CUtlVector< SignifierInfo_t >* GetSignifierQueue( void )
  91. {
  92. ASSERT_LOCAL_PLAYER_RESOLVABLE();
  93. return &(g_SignifierQueue[ GET_ACTIVE_SPLITSCREEN_SLOT() ]);
  94. }
  95. void TeamPingColor( int nTeamNumber, Vector &vColor )
  96. {
  97. Color color = Color( GLOW_OUTLINE_COLOR[0], GLOW_OUTLINE_COLOR[1], GLOW_OUTLINE_COLOR[2] );
  98. if ( nTeamNumber == TEAM_RED )
  99. {
  100. color = UTIL_Portal_Color( 2, 0 ); //orange
  101. }
  102. else
  103. {
  104. color = UTIL_Portal_Color( 1, 0 ); //blue
  105. }
  106. vColor.x = (float)color.r()/255.0f;
  107. vColor.y = (float)color.g()/255.0f;
  108. vColor.z = (float)color.b()/255.0f;
  109. }
  110. //--------------------------------------------------------------------------------------------------------
  111. int AddGlowToObject( C_BaseEntity *pObject, int nTeamNumber )
  112. {
  113. if ( pObject == NULL || pObject->GetRenderAlpha() <= 0 )
  114. return -1;
  115. // Determine if this entity uses the glow capability
  116. ISignifierTarget *pSignifier = dynamic_cast<ISignifierTarget *>(pObject);
  117. if ( pSignifier != NULL )
  118. {
  119. if ( pSignifier->UseSelectionGlow() == false )
  120. return -1;
  121. }
  122. Vector vColor;
  123. TeamPingColor( nTeamNumber, vColor );
  124. return g_GlowObjectManager.RegisterGlowObject( pObject, vColor, GLOW_OUTLINE_ALPHA, GET_ACTIVE_SPLITSCREEN_SLOT() );
  125. }
  126. void RadialMenuMouseCallback( uint8* pData, size_t iSize )
  127. {
  128. // make sure we're getting as much data as we expect
  129. Assert( iSize == sizeof(int)*2 );
  130. // check for null pointer
  131. if ( pData == NULL )
  132. return;
  133. // capture the data
  134. int nCursorX = ((int*)pData)[0];
  135. int nCursorY = ((int*)pData)[1];
  136. // get the radial menu and give it the cursor position
  137. CRadialMenu *pRadialMenu = GET_HUDELEMENT( CRadialMenu );
  138. if ( pRadialMenu )
  139. {
  140. pRadialMenu->SetDemoCursorPos( nCursorX, nCursorY );
  141. }
  142. }
  143. void ClientMenuManager::AddMenuFile( const char *filename )
  144. {
  145. if ( !m_menuKeys )
  146. {
  147. m_menuKeys = new KeyValues( "ClientMenu" );
  148. }
  149. KeyValues *data = new KeyValues( "ClientMenu" );
  150. if ( !data->LoadFromFile( filesystem, filename ) )
  151. {
  152. Warning( "Could not load client menu %s\n", filename );
  153. data->deleteThis();
  154. return;
  155. }
  156. KeyValues *menuKey = data->GetFirstTrueSubKey();
  157. while ( menuKey )
  158. {
  159. if ( m_menuKeys->FindKey( menuKey->GetName(), false ) == NULL )
  160. {
  161. data->RemoveSubKey( menuKey );
  162. m_menuKeys->AddSubKey( menuKey );
  163. }
  164. menuKey = data->GetFirstTrueSubKey();
  165. }
  166. data->deleteThis();
  167. }
  168. KeyValues *ClientMenuManager::FindMenu( const char *menuName )
  169. {
  170. // do not show the menu if the player is dead or is an observer
  171. C_BasePlayer *player = C_BasePlayer::GetLocalPlayer();
  172. if ( !player )
  173. return NULL;
  174. // Find our menu, honoring Alive/Dead/Team suffixes
  175. /*
  176. const char *teamSuffix = player->GetTeam()->Get_Name();
  177. const char *lifeSuffix = player->IsAlive() ? "Alive" : "Dead";
  178. CFmtStr str;
  179. const char *fullMenuName = str.sprintf( "%s,%s,%s", menuName, teamSuffix, lifeSuffix );
  180. */
  181. CFmtStr str;
  182. const char *fullMenuName = str.sprintf( "%s,%s", "Command", menuName );
  183. KeyValues *menuKey = m_menuKeys->FindKey( fullMenuName, false );
  184. if ( !menuKey )
  185. {
  186. fullMenuName = menuName;
  187. menuKey = m_menuKeys->FindKey( fullMenuName, false );
  188. }
  189. return menuKey;
  190. }
  191. void ClientMenuManager::Flush( void )
  192. {
  193. Reset();
  194. AddMenuFile( "scripts/RadialMenu.txt" );
  195. }
  196. //-------------------------------------------------------------------------------------------------------
  197. KeyValues *ClientMenuManagerPlaytest::FindMenu( const char *menuName )
  198. {
  199. return ClientMenuManager::FindMenu( menuName );
  200. }
  201. //-------------------------------------------------------------------------------------------------------
  202. void ClientMenuManagerPlaytest::Flush( void )
  203. {
  204. Reset();
  205. AddMenuFile( "scripts/RadialMenuPlaytest.txt" );
  206. }
  207. void ShowRadialMenuPanel( bool bShow )
  208. {
  209. if ( IsPC() )
  210. {
  211. GetViewPortInterface()->ShowPanel( PANEL_RADIAL_MENU, bShow );
  212. }
  213. }
  214. CRadialMenuPanel::CRadialMenuPanel(IViewPort *pViewPort) : BaseClass(NULL, PANEL_RADIAL_MENU)
  215. {
  216. m_pViewPort = pViewPort;
  217. // load the new scheme early!!
  218. SetScheme("ClientScheme");
  219. SetMoveable(false);
  220. SetSizeable(false);
  221. SetCursor( NULL );
  222. LoadControlSettings("Resource/UI/RadialMenuPanel.res");
  223. InvalidateLayout();
  224. SetPaintBackgroundEnabled( false );
  225. }
  226. void CRadialMenuPanel::ShowPanel( bool bShow )
  227. {
  228. if ( BaseClass::IsVisible() == bShow )
  229. return;
  230. if ( bShow )
  231. {
  232. Activate();
  233. SetMouseInputEnabled( true );
  234. }
  235. else
  236. {
  237. SetVisible( false );
  238. SetMouseInputEnabled( false );
  239. }
  240. m_pViewPort->ShowBackGround( false );
  241. }
  242. float CRadialMenu::m_fLastPingTime[ MAX_SPLITSCREEN_PLAYERS ][ 2 ] = { { 0.0f, 0.0f }, { 0.0f, 0.0f } };
  243. int CRadialMenu::m_nNumPings[ MAX_SPLITSCREEN_PLAYERS ][ 2 ] = { { 0, 0 }, { 0, 0 } };
  244. DECLARE_HUDELEMENT( CRadialMenu );
  245. //--------------------------------------------------------------------------------------------------------------
  246. CRadialMenu::CRadialMenu( const char *pElementName )
  247. : CHudElement( pElementName ), BaseClass( NULL, "RadialMenu" )
  248. {
  249. SetParent( GetClientMode()->GetViewport() );
  250. SetHiddenBits( HIDEHUD_PLAYERDEAD );
  251. MEM_ALLOC_CREDIT();
  252. // initialize dialog
  253. m_resource = new KeyValues( "RadialMenu" );
  254. m_resource->LoadFromFile( filesystem, "resource/UI/RadialMenu.res" );
  255. m_menuData = NULL;
  256. FlushClientMenus();
  257. // load the new scheme early!!
  258. SetScheme("ClientScheme");
  259. SetProportional(true);
  260. HandleControlSettings();
  261. SetCursor( NULL );
  262. m_nArrowTexture = -1;
  263. m_minButtonX = 0;
  264. m_minButtonY = 0;
  265. m_maxButtonX = 0;
  266. m_maxButtonY = 0;
  267. m_demoCursorX = m_demoCursorY = 0;
  268. m_nEntityGlowIndex = -1;
  269. m_bEditing = false;
  270. m_bDragging = false;
  271. m_nDraggingTaunt = -1;
  272. m_fFadeInTime = 0.0f;
  273. m_fSelectionLockInTime = 0.0f;
  274. m_bEnabled = false;
  275. m_bQuickPingForceClose = false;
  276. vgui::ivgui()->AddTickSignal( GetVPanel() );
  277. }
  278. //--------------------------------------------------------------------------------------------------------
  279. const char *CRadialMenu::ButtonNameFromDir( ButtonDir dir )
  280. {
  281. switch( dir )
  282. {
  283. case CENTER:
  284. return "Center";
  285. case NORTH:
  286. return "North";
  287. case NORTH_EAST:
  288. return "NorthEast";
  289. case EAST:
  290. return "East";
  291. case SOUTH_EAST:
  292. return "SouthEast";
  293. case SOUTH:
  294. return "South";
  295. case SOUTH_WEST:
  296. return "SouthWest";
  297. case WEST:
  298. return "West";
  299. case NORTH_WEST:
  300. return "NorthWest";
  301. }
  302. return "None";
  303. }
  304. //--------------------------------------------------------------------------------------------------------
  305. CRadialMenu::ButtonDir CRadialMenu::DirFromButtonName( const char * name )
  306. {
  307. if ( FStrEq( name, "Center" ) )
  308. return CENTER;
  309. if ( FStrEq( name, "North" ) )
  310. return NORTH;
  311. if ( FStrEq( name, "NorthEast" ) )
  312. return NORTH_EAST;
  313. if ( FStrEq( name, "East" ) )
  314. return EAST;
  315. if ( FStrEq( name, "SouthEast" ) )
  316. return SOUTH_EAST;
  317. if ( FStrEq( name, "South" ) )
  318. return SOUTH;
  319. if ( FStrEq( name, "SouthWest" ) )
  320. return SOUTH_WEST;
  321. if ( FStrEq( name, "West" ) )
  322. return WEST;
  323. if ( FStrEq( name, "NorthWest" ) )
  324. return NORTH_WEST;
  325. return CENTER;
  326. }
  327. //--------------------------------------------------------------------------------------------------------
  328. /**
  329. * Created controls from the resource file. We know how to make a special PolygonButton :)
  330. */
  331. vgui::Panel *CRadialMenu::CreateControlByName( const char *controlName )
  332. {
  333. if( !Q_stricmp( "PolygonButton", controlName ) )
  334. {
  335. vgui::Button *newButton = new CRadialButton( this, NULL );
  336. return newButton;
  337. }
  338. else
  339. {
  340. return BaseClass::CreateControlByName( controlName );
  341. }
  342. }
  343. //--------------------------------------------------------------------------------------------------------
  344. CRadialMenu::~CRadialMenu()
  345. {
  346. m_resource->deleteThis();
  347. if ( m_menuData )
  348. {
  349. m_menuData->deleteThis();
  350. }
  351. if ( vgui::surface() && m_nArrowTexture != -1 )
  352. {
  353. vgui::surface()->DestroyTextureID( m_nArrowTexture );
  354. m_nArrowTexture = -1;
  355. }
  356. }
  357. void CRadialMenu::HandleControlSettings()
  358. {
  359. LoadControlSettings("Resource/UI/RadialMenu.res");
  360. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  361. {
  362. ButtonDir dir = (ButtonDir)i;
  363. const char *buttonName = ButtonNameFromDir( dir );
  364. m_buttons[i] = dynamic_cast<CRadialButton *>(FindChildByName( buttonName ));
  365. if ( m_buttons[i] )
  366. {
  367. m_buttons[i]->SetMouseInputEnabled( true );
  368. }
  369. }
  370. m_armedButtonDir = CENTER;
  371. }
  372. void CRadialMenu::StartDrag( void )
  373. {
  374. if ( m_nDraggingTaunt == -1 )
  375. {
  376. return;
  377. }
  378. m_bDragging = true;
  379. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  380. {
  381. ButtonDir dir = (ButtonDir)i;
  382. const char *buttonName = ButtonNameFromDir( dir );
  383. m_buttons[i] = dynamic_cast<CRadialButton *>(FindChildByName( buttonName ));
  384. if ( m_buttons[i] )
  385. {
  386. m_buttons[i]->SetMouseInputEnabled( false );
  387. }
  388. }
  389. SetArmedButtonDir( CENTER );
  390. }
  391. void CRadialMenu::EndDrag( void )
  392. {
  393. if ( !m_bDragging || m_nDraggingTaunt == -1 )
  394. {
  395. return;
  396. }
  397. int nSwap = -1;
  398. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  399. {
  400. ButtonDir dir = (ButtonDir)i;
  401. const char *buttonName = ButtonNameFromDir( dir );
  402. m_buttons[i] = dynamic_cast<CRadialButton *>(FindChildByName( buttonName ));
  403. if ( m_buttons[i] )
  404. {
  405. m_buttons[i]->SetMouseInputEnabled( true );
  406. int mouseX, mouseY;
  407. vgui::surface()->SurfaceGetCursorPos( mouseX, mouseY );
  408. if ( nSwap == -1 && m_buttons[i]->IsVisible() && m_buttons[i]->IsEnabled() && m_buttons[i]->IsWithinTraverse( mouseX, mouseY, false ) )
  409. {
  410. nSwap = i;
  411. }
  412. }
  413. }
  414. if ( nSwap != -1 && nSwap != CENTER )
  415. {
  416. CUtlVector< TauntStatusData > *pTauntData = GetClientMenuManagerTaunt().GetTauntData();
  417. TauntStatusData *pData = &((*pTauntData)[ m_nDraggingTaunt ]);
  418. const char *pDir = "empty";
  419. switch ( nSwap )
  420. {
  421. case NORTH:
  422. pDir = "North";
  423. break;
  424. case SOUTH:
  425. pDir = "South";
  426. break;
  427. case WEST:
  428. pDir = "West";
  429. break;
  430. case EAST:
  431. pDir = "East";
  432. break;
  433. case NORTH_WEST:
  434. pDir = "NorthWest";
  435. break;
  436. case NORTH_EAST:
  437. pDir = "NorthEast";
  438. break;
  439. case SOUTH_WEST:
  440. pDir = "SouthWest";
  441. break;
  442. case SOUTH_EAST:
  443. pDir = "SouthEast";
  444. break;
  445. }
  446. GetClientMenuManagerTaunt().SetTauntPosition( pData->szName, pDir );
  447. //GetClientMenuManagerTaunt().UpdateStorageChange( pData, GetClientMenuManagerTaunt().UPDATE_STORAGE_EQUIPSLOT );
  448. KeyValues *menuKey = GetClientMenuManagerTaunt().FindMenu( "Default" );
  449. if ( menuKey )
  450. {
  451. SetData( menuKey );
  452. }
  453. }
  454. m_bDragging = false;
  455. m_nDraggingTaunt = -1;
  456. }
  457. //--------------------------------------------------------------------------------------------------------
  458. /**
  459. * The radial menu should cover the entire screen to capture mouse input, so we should have a blank
  460. * background.
  461. */
  462. void CRadialMenu::ApplySchemeSettings( vgui::IScheme *pScheme )
  463. {
  464. HandleControlSettings();
  465. BaseClass::ApplySchemeSettings( pScheme );
  466. SetBgColor( Color( 0, 0, 0, 0 ) );
  467. m_lineColor = pScheme->GetColor( "rosette.LineColor", Color( 192, 192, 192, 128 ) );
  468. if ( RadialMenuDebug.GetBool() )
  469. {
  470. SetCursor( vgui::dc_crosshair );
  471. }
  472. else
  473. {
  474. SetCursor( NULL );
  475. }
  476. // Restore button names/commands
  477. if ( m_menuData )
  478. {
  479. SetData( m_menuData );
  480. }
  481. SetAlpha( 0 );
  482. }
  483. void CRadialMenu::PerformLayout( void )
  484. {
  485. SetRadialMenuEnabled( false );
  486. BaseClass::PerformLayout();
  487. }
  488. //--------------------------------------------------------------------------------------------------------
  489. void CRadialMenu::ShowPanel( bool show )
  490. {
  491. if ( IsVisible() == show )
  492. return;
  493. if ( show )
  494. {
  495. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  496. {
  497. if ( !m_buttons[i] )
  498. continue;
  499. m_buttons[i]->SetArmed( false );
  500. m_buttons[i]->SetFakeArmed( false );
  501. m_buttons[i]->SetChosen( false );
  502. }
  503. SetMouseInputEnabled( true );
  504. m_cursorX = m_cursorY = 0;
  505. m_demoCursorX = m_demoCursorY = 0;
  506. m_bFirstCentering = true;
  507. }
  508. else
  509. {
  510. SetRadialMenuEnabled( false );
  511. SetMouseInputEnabled( false );
  512. }
  513. }
  514. void CRadialMenu::PaintBackground( void )
  515. {
  516. int nSlot = vgui::ipanel()->GetMessageContextId( GetVPanel() );
  517. ACTIVE_SPLITSCREEN_PLAYER_GUARD( nSlot );
  518. if ( m_armedButtonDir != CENTER || !m_buttons[ CENTER ]->IsVisible() )
  519. {
  520. int x, y, wide, tall;
  521. GetBounds( x, y, wide, tall );
  522. float fCenterX = x + wide/2;
  523. float fCenterY = y + tall/2;
  524. int nCursorX, nCursorY;
  525. if ( !input->ControllerModeActive() )
  526. {
  527. nCursorX = m_cursorX;
  528. nCursorY = m_cursorY;
  529. }
  530. else
  531. {
  532. float fJoyForward, fJoySide, fJoyPitch, fJoyYaw = 0.0f;
  533. input->Joystick_Querry( fJoyForward, fJoySide, fJoyPitch, fJoyYaw );
  534. // Replace if the other stick was pushed further
  535. // We need to use both sticks because they might have southpaw or legacy set
  536. if ( fabsf( fJoyForward ) > fabsf( fJoyPitch ) )
  537. {
  538. fJoyPitch = fJoyForward;
  539. }
  540. else
  541. {
  542. // Reflip it if the y was inverted because this shouldn't be inverted
  543. static SplitScreenConVarRef s_joy_inverty( "joy_inverty" );
  544. if ( s_joy_inverty.IsValid() && s_joy_inverty.GetBool( nSlot ) )
  545. {
  546. fJoyPitch *= -1.0f;
  547. }
  548. }
  549. // Replace if the other stick was pushed further
  550. // We need to use both sticks because they might have southpaw or legacy set
  551. if ( fabsf( fJoySide ) > fabsf( fJoyYaw ) )
  552. {
  553. fJoyYaw = fJoySide;
  554. }
  555. if ( fJoyPitch > 0.1f || fJoyYaw > 0.1f )
  556. {
  557. nCursorX = fCenterX + fJoyYaw * 100.0f;
  558. nCursorY = fCenterY + fJoyPitch * 100.0f;
  559. }
  560. else
  561. {
  562. int buttonX, buttonY;
  563. m_buttons[ m_armedButtonDir ]->GetIcon()->GetPos( nCursorX, nCursorY );
  564. m_buttons[ m_armedButtonDir ]->GetPos( buttonX, buttonY );
  565. nCursorX += buttonX + m_buttons[ m_armedButtonDir ]->GetIcon()->GetWide() / 2;
  566. nCursorY += buttonY + m_buttons[ m_armedButtonDir ]->GetIcon()->GetTall() / 2;
  567. }
  568. }
  569. Vector2D vNormal( nCursorX - fCenterX, nCursorY - fCenterY );
  570. if ( vNormal.IsZero() )
  571. {
  572. vNormal = Vector2D( 0.0f, -1.0f );
  573. }
  574. Vector2DNormalize( vNormal );
  575. Vector2D vDown( vNormal.y, -vNormal.x );
  576. float innerRadius = cl_rosette_line_inner_radius.GetFloat();
  577. float outerRadius = cl_rosette_line_outer_radius.GetFloat();
  578. innerRadius = YRES( innerRadius ) * 0.75f;
  579. outerRadius = YRES( outerRadius );
  580. float fSize = ( outerRadius - innerRadius ) * 0.5f;
  581. float fOffset = innerRadius + fSize;
  582. fCenterX += vNormal.x * fOffset;
  583. fCenterY += vNormal.y * fOffset;
  584. vgui::Vertex_t points[4] =
  585. {
  586. vgui::Vertex_t( Vector2D( fCenterX, fCenterY ) - vDown * fSize - vNormal * fSize, Vector2D(0,0) ), // Top Left
  587. vgui::Vertex_t( Vector2D( fCenterX, fCenterY ) + vDown * fSize - vNormal * fSize, Vector2D(0,1) ), // Bottom Left
  588. vgui::Vertex_t( Vector2D( fCenterX, fCenterY ) + vDown * fSize + vNormal * fSize, Vector2D(1,1) ), // Bottom Right
  589. vgui::Vertex_t( Vector2D( fCenterX, fCenterY ) - vDown * fSize + vNormal * fSize, Vector2D(1,0) ) // Top Right
  590. };
  591. vgui::surface()->DrawSetColor( 255, 255, 255, 255 );
  592. vgui::surface()->DrawSetTexture( m_nArrowTexture );
  593. vgui::surface()->DrawTexturedPolygon( 4, points );
  594. }
  595. BaseClass::PaintBackground();
  596. }
  597. //--------------------------------------------------------------------------------------------------------
  598. void CRadialMenu::Paint( void )
  599. {
  600. int nBaseAlpha = 0;
  601. float fFade = gpGlobals->curtime - m_fFadeInTime;
  602. if ( fFade > 0.0f )
  603. {
  604. fFade = MIN( 1.0f, fFade * 10.0f );
  605. nBaseAlpha = fFade * 255.0f;
  606. }
  607. const float fadeDuration = 0.1f;
  608. if ( m_fading )
  609. {
  610. float fadeTimeRemaining = m_fadeStart + fadeDuration - gpGlobals->curtime;
  611. int alpha = nBaseAlpha * fadeTimeRemaining / fadeDuration;
  612. SetAlpha( alpha );
  613. if ( alpha <= 0 )
  614. {
  615. ShowRadialMenuPanel( false );
  616. SetRadialMenuEnabled( false );
  617. return;
  618. }
  619. }
  620. else
  621. {
  622. SetAlpha( nBaseAlpha );
  623. }
  624. int x, y, wide, tall;
  625. GetBounds( x, y, wide, tall );
  626. vgui::surface()->DrawSetColor( m_lineColor );
  627. int centerX = x + wide/2;
  628. int centerY = y + tall/2;
  629. float innerRadius = cl_rosette_line_inner_radius.GetFloat();
  630. float outerRadius = cl_rosette_line_outer_radius.GetFloat();
  631. innerRadius = YRES( innerRadius );
  632. outerRadius = YRES( outerRadius );
  633. // Draw horizontal and vertical lines
  634. if ( m_armedButtonDir != EAST && m_buttons[EAST]->IsVisible() && m_buttons[EAST]->IsEnabled() )
  635. {
  636. vgui::surface()->DrawLine( centerX + innerRadius, centerY, centerX + outerRadius, centerY );
  637. }
  638. if ( m_armedButtonDir != WEST && m_buttons[WEST]->IsVisible() && m_buttons[WEST]->IsEnabled() )
  639. {
  640. vgui::surface()->DrawLine( centerX - innerRadius, centerY, centerX - outerRadius, centerY );
  641. }
  642. if ( m_armedButtonDir != SOUTH && m_buttons[SOUTH]->IsVisible() && m_buttons[SOUTH]->IsEnabled() )
  643. {
  644. vgui::surface()->DrawLine( centerX, centerY + innerRadius, centerX, centerY + outerRadius );
  645. }
  646. if ( m_armedButtonDir != NORTH && m_buttons[NORTH]->IsVisible() && m_buttons[NORTH]->IsEnabled() )
  647. {
  648. vgui::surface()->DrawLine( centerX, centerY - innerRadius, centerX, centerY - outerRadius );
  649. }
  650. // Draw diagonal lines
  651. const float scale = 0.707f; // sqrt(2) / 2
  652. if ( m_armedButtonDir != SOUTH_EAST && m_buttons[SOUTH_EAST]->IsVisible() && m_buttons[SOUTH_EAST]->IsEnabled() )
  653. {
  654. vgui::surface()->DrawLine( centerX + innerRadius * scale, centerY + innerRadius * scale, centerX + outerRadius * scale, centerY + outerRadius * scale );
  655. }
  656. if ( m_armedButtonDir != NORTH_WEST && m_buttons[NORTH_WEST]->IsVisible() && m_buttons[NORTH_WEST]->IsEnabled() )
  657. {
  658. vgui::surface()->DrawLine( centerX - innerRadius * scale, centerY - innerRadius * scale, centerX - outerRadius * scale, centerY - outerRadius * scale );
  659. }
  660. if ( m_armedButtonDir != NORTH_EAST && m_buttons[NORTH_EAST]->IsVisible() && m_buttons[NORTH_EAST]->IsEnabled() )
  661. {
  662. vgui::surface()->DrawLine( centerX + innerRadius * scale, centerY - innerRadius * scale, centerX + outerRadius * scale, centerY - outerRadius * scale );
  663. }
  664. if ( m_armedButtonDir != SOUTH_WEST && m_buttons[SOUTH_WEST]->IsVisible() && m_buttons[SOUTH_WEST]->IsEnabled() )
  665. {
  666. vgui::surface()->DrawLine( centerX - innerRadius * scale, centerY + innerRadius * scale, centerX - outerRadius * scale, centerY + outerRadius * scale );
  667. }
  668. if ( RadialMenuDebug.GetBool() )
  669. {
  670. vgui::surface()->DrawSetColor( m_lineColor );
  671. vgui::surface()->DrawOutlinedRect( x + m_minButtonX, y + m_minButtonY, x + m_maxButtonX, y + m_maxButtonY );
  672. // draw the cursor position
  673. vgui::surface()->DrawLine( m_cursorX -10, m_cursorY, m_cursorX + 10, m_cursorY );
  674. vgui::surface()->DrawLine( m_cursorX , m_cursorY - 10 , m_cursorX, m_cursorY + 10 );
  675. }
  676. BaseClass::Paint();
  677. }
  678. void PlayDeactivateSound()
  679. {
  680. C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
  681. // If we've got the player and they selected a different button
  682. if ( localPlayer )
  683. {
  684. localPlayer->EmitSound( "GameUI.UiCoopHudDeactivate" );
  685. }
  686. }
  687. //--------------------------------------------------------------------------------------------------------
  688. void CRadialMenu::OnMousePressed( vgui::MouseCode code )
  689. {
  690. if ( m_bEditing )
  691. {
  692. if ( code == MOUSE_LEFT )
  693. {
  694. StartDrag();
  695. }
  696. }
  697. if ( code == MOUSE_RIGHT )
  698. {
  699. StartFade();
  700. PlayDeactivateSound();
  701. }
  702. BaseClass::OnMousePressed( code );
  703. }
  704. void CRadialMenu::OnMouseReleased( vgui::MouseCode code )
  705. {
  706. if ( m_bEditing )
  707. {
  708. if ( code == MOUSE_LEFT )
  709. {
  710. EndDrag();
  711. }
  712. }
  713. BaseClass::OnMouseReleased( code );
  714. }
  715. //--------------------------------------------------------------------------------------------------------
  716. void CRadialMenu::OnCommand( const char *command )
  717. {
  718. if ( RadialMenuDebug.GetBool() )
  719. {
  720. Msg( "%f: Clicked on button with command '%s'\n", gpGlobals->curtime, command );
  721. }
  722. if ( !Q_strcmp(command, "done") )
  723. {
  724. C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
  725. if ( localPlayer )
  726. {
  727. localPlayer->EmitSound("MouseMenu.abort");
  728. }
  729. StartFade();
  730. }
  731. else
  732. {
  733. // Undone... people hate clicking to select, so don't let them train themselves to do it
  734. //StartFade();
  735. //SendCommand( command );
  736. }
  737. BaseClass::OnCommand( command );
  738. }
  739. //--------------------------------------------------------------------------------------------------------
  740. void CRadialMenu::SetArmedButtonDir( ButtonDir dir )
  741. {
  742. if ( dir != NUM_BUTTON_DIRS )
  743. {
  744. CRadialButton *armedButton = m_buttons[dir];
  745. if ( m_buttons[dir]->GetPassthru() )
  746. {
  747. armedButton = m_buttons[dir]->GetPassthru();
  748. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  749. {
  750. if ( m_buttons[i] == armedButton )
  751. {
  752. dir = (ButtonDir)i;
  753. break;
  754. }
  755. }
  756. }
  757. }
  758. if ( m_buttons[ dir ]->IsEnabled() )
  759. {
  760. C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
  761. // If we've got the player and they selected a different button
  762. if ( localPlayer && dir != m_armedButtonDir )
  763. {
  764. if( dir != CENTER )
  765. {
  766. localPlayer->EmitSound("GameUI.UiCoopHudFocus");
  767. }
  768. else
  769. {
  770. localPlayer->EmitSound("GameUI.UiCoopHudUnfocus");
  771. }
  772. }
  773. }
  774. m_armedButtonDir = dir;
  775. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  776. {
  777. if ( !m_buttons[i] )
  778. continue;
  779. m_buttons[i]->SetFakeArmed( false );
  780. m_buttons[i]->SetArmed( false );
  781. if ( i != m_armedButtonDir )
  782. {
  783. m_buttons[i]->SetChosen( false );
  784. }
  785. }
  786. if ( m_armedButtonDir != NUM_BUTTON_DIRS )
  787. {
  788. if ( m_buttons[m_armedButtonDir] )
  789. {
  790. #ifdef OSX
  791. if ( !m_buttons[m_armedButtonDir]->IsFakeArmed() )
  792. {
  793. m_buttons[m_armedButtonDir]->SetArmed( true );
  794. OnCursorEnteredButton( m_cursorX, m_cursorY, m_buttons[m_armedButtonDir] );
  795. }
  796. #endif
  797. m_buttons[m_armedButtonDir]->SetFakeArmed( true );
  798. }
  799. }
  800. if ( m_armedButtonDir != CENTER )
  801. {
  802. SetFadeInTime( gpGlobals->curtime - 1.0f );
  803. }
  804. }
  805. //--------------------------------------------------------------------------------------------------------
  806. static const char *ButtonDirString( CRadialMenu::ButtonDir dir )
  807. {
  808. switch ( dir )
  809. {
  810. case CRadialMenu::CENTER:
  811. return "CENTER";
  812. case CRadialMenu::NORTH:
  813. return "NORTH";
  814. case CRadialMenu::NORTH_EAST:
  815. return "NORTH_EAST";
  816. case CRadialMenu::EAST:
  817. return "EAST";
  818. case CRadialMenu::SOUTH_EAST:
  819. return "SOUTH_EAST";
  820. case CRadialMenu::SOUTH:
  821. return "SOUTH";
  822. case CRadialMenu::SOUTH_WEST:
  823. return "SOUTH_WEST";
  824. case CRadialMenu::WEST:
  825. return "WEST";
  826. case CRadialMenu::NORTH_WEST:
  827. return "NORTH_WEST";
  828. default:
  829. return "UNKNOWN";
  830. }
  831. }
  832. //--------------------------------------------------------------------------------------------------------
  833. bool IsCurrentMenuTypeDisabled( C_Portal_Player *pPlayer, RadialMenuTypes_t menuType )
  834. {
  835. // if the player isn't valid or is in the middle of taunting
  836. if ( !pPlayer || pPlayer->IsTaunting() )
  837. {
  838. return true;
  839. }
  840. // check the specific type of radial menu
  841. switch ( menuType )
  842. {
  843. case MENU_PING:
  844. if ( pPlayer->IsPingDisabled() )
  845. {
  846. return true;
  847. }
  848. break;
  849. case MENU_TAUNT:
  850. if ( pPlayer->IsTauntDisabled() )
  851. {
  852. return true;
  853. }
  854. break;
  855. case MENU_PLAYTEST:
  856. // don't allow playtest pings where normal pings aren't allowed
  857. if ( pPlayer->IsPingDisabled()
  858. #if defined( PORTAL2_PUZZLEMAKER )
  859. || ( !sv_record_playtest.GetBool() && !engine->IsPlayingDemo() )
  860. #endif // PORTAL2_PUZZLEMAKER
  861. )
  862. {
  863. return true;
  864. }
  865. break;
  866. }
  867. return false;
  868. }
  869. //--------------------------------------------------------------------------------------------------------
  870. void CRadialMenu::OnCursorEnteredButton( int x, int y, CRadialButton *button )
  871. {
  872. ButtonDir nNewDir = NUM_BUTTON_DIRS;
  873. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  874. {
  875. if ( m_buttons[i] == button )
  876. {
  877. m_cursorX = x;
  878. m_cursorY = y;
  879. // If we've got the player and they selected a different button
  880. if ( (ButtonDir)i != m_armedButtonDir )
  881. {
  882. nNewDir = (ButtonDir)i;
  883. break;
  884. }
  885. if ( RadialMenuDebug.GetBool() )
  886. {
  887. Msg( "%f: rosette cursor entered %s at %d,%d\n", gpGlobals->curtime, ButtonDirString( m_armedButtonDir ), x, y );
  888. engine->Con_NPrintf( 20, "%d %d %s", x, y, ButtonDirString( m_armedButtonDir ) );
  889. }
  890. }
  891. }
  892. if ( nNewDir != NUM_BUTTON_DIRS && !input->ControllerModeActive() )
  893. {
  894. SetArmedButtonDir( nNewDir );
  895. }
  896. }
  897. //--------------------------------------------------------------------------------------------------------
  898. void CRadialMenu::UpdateButtonBounds( void )
  899. {
  900. // Save off the extents of our child buttons so we can clip the cursor to that later
  901. int wide, tall;
  902. GetSize( wide, tall );
  903. m_minButtonX = wide;
  904. m_minButtonY = tall;
  905. m_maxButtonX = 0;
  906. m_maxButtonY = 0;
  907. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  908. {
  909. if ( !m_buttons[i] )
  910. continue;
  911. int hotspotMinX = 0;
  912. int hotspotMinY = 0;
  913. int hotspotMaxX = 0;
  914. int hotspotMaxY = 0;
  915. m_buttons[i]->GetHotspotBounds( &hotspotMinX, &hotspotMinY, &hotspotMaxX, &hotspotMaxY );
  916. int buttonX, buttonY;
  917. m_buttons[i]->GetPos( buttonX, buttonY );
  918. m_minButtonX = MIN( m_minButtonX, hotspotMinX + buttonX );
  919. m_minButtonY = MIN( m_minButtonY, hotspotMinY + buttonY );
  920. m_maxButtonX = MAX( m_maxButtonX, hotspotMaxX + buttonX );
  921. m_maxButtonY = MAX( m_maxButtonY, hotspotMaxY + buttonY );
  922. }
  923. // First frame, we won't have any hotspots set up, so we get inverted bounds from our initial setup.
  924. // Reverse these, so our button bounds covers our bounds.
  925. if ( m_minButtonX > m_maxButtonX )
  926. {
  927. V_swap( m_minButtonX, m_maxButtonX );
  928. }
  929. if ( m_minButtonY > m_maxButtonY )
  930. {
  931. V_swap( m_minButtonY, m_maxButtonY );
  932. }
  933. }
  934. //--------------------------------------------------------------------------------------------------------
  935. void CRadialMenu::OnThink( void )
  936. {
  937. int nSlot = vgui::ipanel()->GetMessageContextId( GetVPanel() );
  938. ACTIVE_SPLITSCREEN_PLAYER_GUARD( nSlot );
  939. if ( m_bQuickPingForceClose )
  940. {
  941. SetArmedButtonDir( CENTER );
  942. }
  943. C_Portal_Player *pPlayer = ToPortalPlayer( C_BasePlayer::GetLocalPlayer( nSlot ) );
  944. if ( IsCurrentMenuTypeDisabled( pPlayer, m_menuType ) )
  945. {
  946. CloseRadialMenuCommand( m_menuType );
  947. }
  948. if ( !IsMouseInputEnabled() )
  949. return;
  950. // See if our target entity has vanished
  951. if ( (m_menuType != MENU_TAUNT) && m_hTargetEntity == NULL && m_vPosition == vec3_invalid )
  952. {
  953. StartFade();
  954. return;
  955. }
  956. // See if we need to create our glow index
  957. if ( m_nEntityGlowIndex == -1 )
  958. {
  959. // dont glow players
  960. C_Portal_Player *pPlayer = dynamic_cast<C_Portal_Player *>(m_hTargetEntity.Get());
  961. if ( !pPlayer && m_menuType != MENU_TAUNT && ( m_hTargetEntity.Get() && m_hTargetEntity.Get()->GetRenderAlpha() > 0) )
  962. {
  963. C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
  964. if ( !localPlayer )
  965. return;
  966. m_nEntityGlowIndex = AddGlowToObject( m_hTargetEntity, localPlayer->GetTeamNumber() );
  967. }
  968. }
  969. else if ( m_menuType == MENU_TAUNT )
  970. {
  971. ClearGlowEntity();
  972. }
  973. #if defined(OSX)
  974. int dx, dy;
  975. engine->GetMouseDelta( dx, dy );
  976. m_cursorX += dx;
  977. m_cursorY += dy;
  978. #else
  979. vgui::surface()->SurfaceGetCursorPos( m_cursorX, m_cursorY );
  980. ScreenToLocal( m_cursorX, m_cursorY );
  981. #endif
  982. if ( engine->IsRecordingDemo() )
  983. {
  984. int nMousePos[2] = { m_cursorX, m_cursorY };
  985. engine->RecordDemoCustomData( RadialMenuMouseCallback, &nMousePos, sizeof(int) * 2 );
  986. }
  987. else if ( engine->IsPlayingDemo() )
  988. {
  989. // use saved coords from the callback
  990. m_cursorX = m_demoCursorX;
  991. m_cursorY = m_demoCursorY;
  992. }
  993. int wide, tall;
  994. GetSize( wide, tall );
  995. int centerx = wide / 2;
  996. int centery = tall / 2;
  997. UpdateButtonBounds();
  998. if ( m_bFirstCentering )
  999. {
  1000. #ifndef OSX
  1001. LocalToScreen( centerx, centery );
  1002. vgui::surface()->SurfaceSetCursorPos( centerx, centery );
  1003. #endif
  1004. m_cursorX = centerx;
  1005. m_cursorY = centery;
  1006. SetArmedButtonDir( CENTER );
  1007. m_bFirstCentering = false;
  1008. }
  1009. else
  1010. {
  1011. float cursorDistX = ( m_cursorX - centerx );
  1012. float cursorDistY = ( m_cursorY - centery );
  1013. float buttonRadius = MAX( m_maxButtonX - centerx, m_maxButtonY - centery ) * 0.3f;
  1014. if ( cursorDistX != 0.0f && cursorDistY != 0.0f )
  1015. {
  1016. float cursorDist = sqrt( Sqr(cursorDistX) + Sqr(cursorDistY) );
  1017. // keeping mouse cursor within button radius
  1018. if ( cursorDist > buttonRadius )
  1019. {
  1020. cursorDistX *= ( buttonRadius / cursorDist );
  1021. cursorDistY *= ( buttonRadius / cursorDist );
  1022. m_cursorX = centerx + cursorDistX;
  1023. m_cursorY = centery + cursorDistY;
  1024. }
  1025. #ifndef OSX
  1026. LocalToScreen( m_cursorX, m_cursorY );
  1027. if ( RadialMenuDebug.GetBool() )
  1028. {
  1029. Msg( "%f: rosette warping cursor to %d %d\n", gpGlobals->curtime, m_cursorX, m_cursorY );
  1030. }
  1031. vgui::surface()->SurfaceSetCursorPos( m_cursorX, m_cursorY );
  1032. #endif
  1033. }
  1034. float fJoyForward, fJoySide, fJoyPitch, fJoyYaw = 0.0f;
  1035. if ( input->ControllerModeActive() )
  1036. {
  1037. input->Joystick_Querry( fJoyForward, fJoySide, fJoyPitch, fJoyYaw );
  1038. // Replace if the other stick was pushed further
  1039. // We need to use both sticks because they might have southpaw or legacy set
  1040. if ( fabsf( fJoyForward ) > fabsf( fJoyPitch ) )
  1041. {
  1042. fJoyPitch = fJoyForward;
  1043. }
  1044. else
  1045. {
  1046. // Reflip it if the y was inverted because this shouldn't be inverted
  1047. static SplitScreenConVarRef s_joy_inverty( "joy_inverty" );
  1048. if ( s_joy_inverty.IsValid() && s_joy_inverty.GetBool( nSlot ) )
  1049. {
  1050. fJoyPitch *= -1.0f;
  1051. }
  1052. }
  1053. // Replace if the other stick was pushed further
  1054. // We need to use both sticks because they might have southpaw or legacy set
  1055. if ( fabsf( fJoySide ) > fabsf( fJoyYaw ) )
  1056. {
  1057. fJoyYaw = fJoySide;
  1058. }
  1059. }
  1060. else
  1061. {
  1062. fJoyPitch = cursorDistY / buttonRadius;
  1063. fJoyYaw = cursorDistX / buttonRadius;
  1064. }
  1065. ButtonDir dir = CENTER;
  1066. // Right stick can select
  1067. if ( fabsf( fJoyPitch ) > 0.3f || fabsf( fJoyYaw ) > 0.3f )
  1068. {
  1069. // Right stick is past the dead zone
  1070. float fStickAngle = RAD2DEG( atan2( fJoyYaw, fJoyPitch ) );
  1071. if ( fStickAngle > 157.5f )
  1072. {
  1073. dir = NORTH;
  1074. }
  1075. else if ( fStickAngle > 112.5f )
  1076. {
  1077. dir = NORTH_EAST;
  1078. }
  1079. else if ( fStickAngle > 67.5f )
  1080. {
  1081. dir = EAST;
  1082. }
  1083. else if ( fStickAngle > 22.5f )
  1084. {
  1085. dir = SOUTH_EAST;
  1086. }
  1087. else if ( fStickAngle > -22.5f )
  1088. {
  1089. dir = SOUTH;
  1090. }
  1091. else if ( fStickAngle > -67.5f )
  1092. {
  1093. dir = SOUTH_WEST;
  1094. }
  1095. else if ( fStickAngle > -112.5f )
  1096. {
  1097. dir = WEST;
  1098. }
  1099. else if ( fStickAngle > -157.5f )
  1100. {
  1101. dir = NORTH_WEST;
  1102. }
  1103. else
  1104. {
  1105. dir = NORTH;
  1106. }
  1107. }
  1108. CRadialButton *pButton = m_buttons[dir];
  1109. if ( pButton && pButton->IsVisible() && pButton->IsEnabled() && !pButton->IsArmed() && m_armedButtonDir != dir )
  1110. {
  1111. // Only allow going back to center if a tiny amount of time has passed
  1112. if ( !input->ControllerModeActive() || m_fSelectionLockInTime == 0.0f || gpGlobals->curtime < m_fSelectionLockInTime + cl_rosette_gamepad_lockin_time.GetFloat() || dir != CENTER )
  1113. {
  1114. m_fSelectionLockInTime = gpGlobals->curtime;
  1115. pButton->SetMaxScale( dir == CENTER ? 1.0f : 0.75f );
  1116. SetArmedButtonDir( dir );
  1117. }
  1118. }
  1119. if ( m_armedButtonDir != CENTER && m_armedButtonDir != NUM_BUTTON_DIRS )
  1120. {
  1121. float fInterp = RemapValClamped( ( gpGlobals->curtime - m_fSelectionLockInTime ), 0.0f, cl_rosette_gamepad_expand_time.GetFloat(), 0.0f, 1.0f );
  1122. CRadialButton *pSelectedButton = m_buttons[ m_armedButtonDir ];
  1123. pSelectedButton->SetMaxScale( 0.75f + 0.25f * fInterp );
  1124. if ( input->ControllerModeActive() && fInterp >= 1.0f )
  1125. {
  1126. // take action as soon as the player select something on the menu
  1127. CloseRadialMenuCommand( m_menuType );
  1128. }
  1129. }
  1130. }
  1131. }
  1132. void CRadialMenu::OnTick( void )
  1133. {
  1134. BaseClass::OnTick();
  1135. for ( int nPlayer = 0; nPlayer < MAX_SPLITSCREEN_PLAYERS; ++nPlayer )
  1136. {
  1137. ACTIVE_SPLITSCREEN_PLAYER_GUARD( nPlayer );
  1138. CUtlVector< SignifierInfo_t > *pSignifierQueue = GetSignifierQueue();
  1139. for ( int i = pSignifierQueue->Count() - 1; i >= 0; --i )
  1140. {
  1141. if ( (*pSignifierQueue)[ i ].fDisplayTime <= gpGlobals->curtime )
  1142. {
  1143. AddLocator( (*pSignifierQueue)[ i ].hTarget, (*pSignifierQueue)[ i ].vPos, (*pSignifierQueue)[ i ].vNormal, (*pSignifierQueue)[ i ].nPlayerIndex, (*pSignifierQueue)[ i ].szCaption, 0.0f );
  1144. pSignifierQueue->Remove( i );
  1145. }
  1146. }
  1147. }
  1148. }
  1149. static const char *s_pszRadialMenuIgnoreActions[] =
  1150. {
  1151. "forward",
  1152. "back",
  1153. "moveleft",
  1154. "moveright",
  1155. "jump",
  1156. "duck",
  1157. "attack",
  1158. "attack2"
  1159. };
  1160. int CRadialMenu::KeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding )
  1161. {
  1162. static bool bViewLocked = false;
  1163. if ( !IsVisible() )
  1164. return 1;
  1165. if ( !down )
  1166. return 1;
  1167. ASSERT_LOCAL_PLAYER_RESOLVABLE();
  1168. int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
  1169. int numIgnore = ARRAYSIZE( s_pszRadialMenuIgnoreActions );
  1170. for ( int i=0; i<numIgnore; ++i )
  1171. {
  1172. int count = 0;
  1173. ButtonCode_t key;
  1174. do
  1175. {
  1176. key = (ButtonCode_t)engine->Key_CodeForBinding( s_pszRadialMenuIgnoreActions[i], nSlot, count, -1 );
  1177. if ( IsJoystickCode( key ) )
  1178. {
  1179. key = GetBaseButtonCode( key );
  1180. }
  1181. if ( keynum == key )
  1182. {
  1183. return 0;
  1184. }
  1185. ++count;
  1186. } while ( key != BUTTON_CODE_INVALID );
  1187. }
  1188. return 1;
  1189. }
  1190. //--------------------------------------------------------------------------------------------------------
  1191. void CRadialMenu::SendCommand( const char *commandStr )
  1192. {
  1193. if ( V_strcmp( commandStr, "done" ) == 0 )
  1194. {
  1195. return;
  1196. }
  1197. // Setup the basic command
  1198. char szClientCmd[512];
  1199. // Tack on our target entity index
  1200. int nEntityIndex = ( m_hTargetEntity ) ? m_hTargetEntity->entindex() : -1;
  1201. bool bDelayed = false;
  1202. bool bIsTaunt = false;
  1203. if ( StringHasPrefix( commandStr, "taunt" ) )
  1204. {
  1205. bIsTaunt = true;
  1206. V_strcpy( szClientCmd, commandStr );
  1207. const char *pchTaunt = commandStr + V_strlen( "taunt" );
  1208. if ( pchTaunt[ 0 ] == ' ' && pchTaunt[ 1 ] != '\0' )
  1209. {
  1210. pchTaunt++;
  1211. GetClientMenuManagerTaunt().IsTauntTeam( pchTaunt );
  1212. GetClientMenuManagerTaunt().SetTauntUsed( pchTaunt );
  1213. }
  1214. }
  1215. else
  1216. {
  1217. if ( V_strcmp( commandStr, "countdown" ) == 0 )
  1218. {
  1219. bDelayed = true;
  1220. if ( GetSignifierQueue()->Count() )
  1221. {
  1222. // Don't start a new count till the old one finished!
  1223. return;
  1224. }
  1225. }
  1226. // Build a super string (ick)
  1227. V_snprintf( szClientCmd, sizeof( szClientCmd ), "signify %s %d %.2f %.2f %.2f %.2f %.2f %.2f %.2f",
  1228. commandStr,
  1229. nEntityIndex,
  1230. ( bDelayed ? 0.3f : 0.0f ),
  1231. m_vPosition.x, m_vPosition.y, m_vPosition.z,
  1232. m_vNormal.x, m_vNormal.y, m_vNormal.z );
  1233. }
  1234. // Send it on its way
  1235. engine->ClientCmd( szClientCmd );
  1236. if ( !bDelayed && !bIsTaunt )
  1237. {
  1238. // Now send to ourself first
  1239. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
  1240. if ( pPlayer )
  1241. {
  1242. AddLocator( m_hTargetEntity, m_vPosition, m_vNormal, pPlayer->entindex(), commandStr, 0.0f );
  1243. }
  1244. }
  1245. }
  1246. //--------------------------------------------------------------------------------------------------------
  1247. void CRadialMenu::ChooseArmedButton()
  1248. {
  1249. StartFade();
  1250. CRadialButton *button = NULL;
  1251. for ( int i=NUM_BUTTON_DIRS-1; i>=0; --i )
  1252. {
  1253. if ( !m_buttons[i] )
  1254. continue;
  1255. if ( m_buttons[i]->IsVisible() && m_buttons[i]->IsEnabled() && m_buttons[i]->IsArmed() && !m_buttons[i]->GetPassthru() )
  1256. {
  1257. if ( RadialMenuDebug.GetBool() )
  1258. {
  1259. Msg( "%f: Choosing armed button %s\n", gpGlobals->curtime, ButtonDirString( (ButtonDir)i ) );
  1260. }
  1261. button = m_buttons[i];
  1262. break;
  1263. }
  1264. }
  1265. if ( !button && m_armedButtonDir != NUM_BUTTON_DIRS )
  1266. {
  1267. if ( m_buttons[m_armedButtonDir] && m_buttons[m_armedButtonDir]->IsVisible() && m_buttons[m_armedButtonDir]->IsEnabled() )
  1268. {
  1269. if ( RadialMenuDebug.GetBool() )
  1270. {
  1271. Msg( "%f: Choosing saved armed button %s\n", gpGlobals->curtime, ButtonDirString( m_armedButtonDir ) );
  1272. }
  1273. button = m_buttons[m_armedButtonDir];
  1274. }
  1275. }
  1276. if ( button )
  1277. {
  1278. KeyValues *command = button->GetCommand();
  1279. if ( command )
  1280. {
  1281. const char *commandStr = command->GetString( "command", NULL );
  1282. if ( commandStr )
  1283. {
  1284. button->SetChosen( true );
  1285. SendCommand( commandStr );
  1286. if ( button->GetGLaDOSResponse() > 0 )
  1287. {
  1288. C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
  1289. if ( localPlayer )
  1290. {
  1291. char szCmd[ 32 ];
  1292. V_snprintf( szCmd, sizeof(szCmd), "CoopPingTool(%i,%i)", ( localPlayer->GetTeamNumber() == TEAM_RED ? 1 : 2 ), button->GetGLaDOSResponse() );
  1293. engine->ClientCmd( szCmd );
  1294. }
  1295. }
  1296. }
  1297. }
  1298. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  1299. {
  1300. if ( !m_buttons[i] )
  1301. continue;
  1302. if ( m_buttons[i] == button )
  1303. {
  1304. if( m_menuType == MENU_TAUNT && i == CENTER )
  1305. {
  1306. PlayDeactivateSound();
  1307. }
  1308. continue;
  1309. }
  1310. m_buttons[i]->SetFakeArmed( false );
  1311. m_buttons[i]->SetChosen( false );
  1312. }
  1313. }
  1314. }
  1315. //--------------------------------------------------------------------------------------------------------
  1316. void CRadialMenu::StartFade( void )
  1317. {
  1318. m_fading = true;
  1319. m_fadeStart = gpGlobals->curtime;
  1320. SetMouseInputEnabled( false );
  1321. ClearGlowEntity();
  1322. }
  1323. //--------------------------------------------------------------------------------------------------------
  1324. void CRadialMenu::SetData( KeyValues *data )
  1325. {
  1326. if ( !data )
  1327. return;
  1328. if ( RadialMenuDebug.GetBool() )
  1329. {
  1330. m_resource->deleteThis();
  1331. m_resource = new KeyValues( "RadialMenu" );
  1332. m_resource->LoadFromFile( filesystem, "resource/UI/RadialMenu.res" );
  1333. }
  1334. if ( m_menuData != data )
  1335. {
  1336. if ( m_menuData )
  1337. {
  1338. m_menuData->deleteThis();
  1339. }
  1340. m_menuData = data->MakeCopy();
  1341. }
  1342. bool bDisabledButtons[ NUM_BUTTON_DIRS ];
  1343. memset( bDisabledButtons, 0, sizeof( bDisabledButtons ) );
  1344. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  1345. {
  1346. if ( !m_buttons[i] )
  1347. continue;
  1348. ButtonDir dir = (ButtonDir)i;
  1349. const char *buttonName = ButtonNameFromDir( dir );
  1350. KeyValues *buttonData = data->FindKey( buttonName, false );
  1351. if ( !buttonData )
  1352. {
  1353. m_buttons[i]->SetVisible( false );
  1354. continue;
  1355. }
  1356. KeyValues *resourceControl = m_resource->FindKey( buttonName, false );
  1357. if ( resourceControl )
  1358. {
  1359. m_buttons[i]->UpdateHotspots( resourceControl );
  1360. m_buttons[i]->InvalidateLayout();
  1361. }
  1362. m_buttons[i]->SetVisible( true );
  1363. m_buttons[i]->SetChosen( false );
  1364. const char *text = buttonData->GetString( "text" );
  1365. m_buttons[i]->SetText( text );
  1366. const char *command = buttonData->GetString( "command" );
  1367. m_buttons[i]->SetCommand( command );
  1368. const char *image = buttonData->GetString( "icon" );
  1369. if ( image && image[0] != '\0' )
  1370. {
  1371. const char *image2 = buttonData->GetString( "icon2" );
  1372. if ( image2 && image2[0] != '\0' )
  1373. {
  1374. // Lets decide which image to use based on team
  1375. C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
  1376. if ( localPlayer && localPlayer->GetTeamNumber() == TEAM_RED )
  1377. {
  1378. m_buttons[i]->SetImage( image2 );
  1379. }
  1380. else
  1381. {
  1382. m_buttons[i]->SetImage( image );
  1383. }
  1384. }
  1385. else
  1386. {
  1387. // Only one image possible
  1388. m_buttons[i]->SetImage( image );
  1389. }
  1390. }
  1391. if ( !command || !*command )
  1392. {
  1393. bDisabledButtons[ i ] = true;
  1394. m_buttons[i]->SetEnabled( false );
  1395. m_buttons[i]->SetPulse( false );
  1396. }
  1397. else
  1398. {
  1399. m_buttons[i]->SetEnabled( true );
  1400. m_buttons[i]->SetPulse( false );
  1401. //m_buttons[i]->SetPulse( buttonData->GetBool( "new" ) );
  1402. }
  1403. m_buttons[i]->SetGLaDOSResponse( buttonData->GetInt( "glados", 0 ) );
  1404. m_buttons[i]->ShowSubmenuIndicator( Q_strncasecmp( "radialmenu ", command, Q_strlen( "radialmenu " ) ) == 0 );
  1405. const char *owner = buttonData->GetString( "owner", NULL );
  1406. if ( owner )
  1407. {
  1408. ButtonDir dir = DirFromButtonName( owner );
  1409. m_buttons[i]->SetPassthru( m_buttons[dir] );
  1410. }
  1411. else
  1412. {
  1413. m_buttons[i]->SetPassthru( NULL );
  1414. }
  1415. m_buttons[i]->SetArmed( false );
  1416. }
  1417. }
  1418. //--------------------------------------------------------------------------------------------------------
  1419. void CRadialMenu::ClearGlowEntity( void )
  1420. {
  1421. // Stop glowing if we're done
  1422. if ( m_nEntityGlowIndex != -1 )
  1423. {
  1424. g_GlowObjectManager.UnregisterGlowObject( m_nEntityGlowIndex );
  1425. m_nEntityGlowIndex = -1;
  1426. }
  1427. }
  1428. //--------------------------------------------------------------------------------------------------------
  1429. void CRadialMenu::OnRadialMenuOpen( void )
  1430. {
  1431. if ( m_nArrowTexture == -1 )
  1432. {
  1433. m_nArrowTexture = vgui::surface()->CreateNewTextureID();
  1434. vgui::surface()->DrawSetTextureFile( m_nArrowTexture, RADIAL_MENU_POINTER_TEXTURE, true, false );
  1435. }
  1436. m_fading = false;
  1437. m_fadeStart = 0.0f;
  1438. vgui::Button *firstButton = NULL;
  1439. for ( int i=0; i<NUM_BUTTON_DIRS; ++i )
  1440. {
  1441. if ( !m_buttons[i] || !m_buttons[i]->IsVisible() || !m_buttons[i]->IsEnabled() )
  1442. continue;
  1443. if ( firstButton )
  1444. {
  1445. // already found another valid button. since we have at least 2, we can show the menu.
  1446. SetMouseInputEnabled( true );
  1447. return;
  1448. }
  1449. firstButton = m_buttons[i];
  1450. }
  1451. }
  1452. //--------------------------------------------------------------------------------------------------------
  1453. void FlushClientMenus( void )
  1454. {
  1455. TheClientMenuManager.Flush();
  1456. TheClientMenuManagerPlaytest.Flush();
  1457. for ( int i = 0; i < MAX_SPLITSCREEN_PLAYERS; ++i )
  1458. {
  1459. GetClientMenuManagerTaunt( i ).Flush();
  1460. }
  1461. }
  1462. //--------------------------------------------------------------------------------------------------------
  1463. void OpenRadialMenu( const char *lpszTargetClassification, EHANDLE hTargetEntity, const Vector &vPosition, const Vector &vNormal, RadialMenuTypes_t menuType )
  1464. {
  1465. // FIXME: Need the equivalent here...
  1466. // if ( GetTerrorClientMode() && GetTerrorClientMode()->IsInTransition() )
  1467. // return;
  1468. ASSERT_LOCAL_PLAYER_RESOLVABLE();
  1469. int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
  1470. C_Portal_Player *localPlayer = ToPortalPlayer( C_BasePlayer::GetLocalPlayer( nSlot ) );
  1471. if ( IsCurrentMenuTypeDisabled( localPlayer, menuType ) )
  1472. {
  1473. return;
  1474. }
  1475. CRadialMenu *pRadialMenu = GET_HUDELEMENT( CRadialMenu );
  1476. if ( !pRadialMenu )
  1477. return;
  1478. pRadialMenu->SetQuickPingForceClose( false );
  1479. const char *pchTarget = ( ( lpszTargetClassification && lpszTargetClassification[ 0 ] != '\0' ) ? ( lpszTargetClassification ) : ( "Default" ) );
  1480. if ( FStrEq( s_radialMenuName[ nSlot ], pchTarget ) )
  1481. {
  1482. bool wasOpen = pRadialMenu->IsVisible() && !pRadialMenu->IsFading();
  1483. if ( wasOpen )
  1484. {
  1485. pRadialMenu->SetRadialType( menuType );
  1486. return;
  1487. }
  1488. }
  1489. if ( RadialMenuDebug.GetBool() )
  1490. {
  1491. FlushClientMenus(); // for now, reload every time
  1492. }
  1493. // Msg("Hit: %s\n", pchTarget );
  1494. ClientMenuManager *pMM;
  1495. if ( menuType == MENU_TAUNT )
  1496. {
  1497. pMM = &GetClientMenuManagerTaunt();
  1498. }
  1499. else if ( menuType == MENU_PING )
  1500. {
  1501. pMM = &TheClientMenuManager;
  1502. }
  1503. else
  1504. {
  1505. pMM = &TheClientMenuManagerPlaytest;
  1506. }
  1507. KeyValues *menuKey = pMM->FindMenu( pchTarget );
  1508. if ( menuKey == NULL )
  1509. {
  1510. menuKey = pMM->FindMenu( "Default" );
  1511. if ( menuKey == NULL )
  1512. {
  1513. DevMsg( "No client menu currently matches %s\n", pchTarget );
  1514. ShowRadialMenuPanel( false );
  1515. pRadialMenu->SetRadialMenuEnabled( false );
  1516. return;
  1517. }
  1518. }
  1519. V_snprintf( s_radialMenuName[ nSlot ], sizeof( s_radialMenuName[ nSlot ] ), pchTarget );
  1520. pRadialMenu->SetData( menuKey );
  1521. pRadialMenu->SetRadialType( menuType );
  1522. if ( menuType == MENU_TAUNT || input->ControllerModeActive() )
  1523. {
  1524. pRadialMenu->SetFadeInTime( gpGlobals->curtime - 1.0f );
  1525. }
  1526. else
  1527. {
  1528. pRadialMenu->SetFadeInTime( gpGlobals->curtime + 0.5f );
  1529. }
  1530. pRadialMenu->ClearLockInTime();
  1531. pRadialMenu->m_bFirstCentering = true;
  1532. // Send up the specific data we need passed in
  1533. pRadialMenu->SetTargetEntity( hTargetEntity );
  1534. pRadialMenu->SetTraceData( vPosition, vNormal );
  1535. if ( localPlayer )
  1536. {
  1537. localPlayer->EmitSound( "GameUI.UiCoopHudActivate" );
  1538. }
  1539. ShowRadialMenuPanel( true );
  1540. pRadialMenu->SetRadialMenuEnabled( true );
  1541. pRadialMenu->OnRadialMenuOpen();
  1542. }
  1543. //-----------------------------------------------------------------------------
  1544. // Purpose: Find and categorize our intended command target and let the client know
  1545. //-----------------------------------------------------------------------------
  1546. bool LaunchRadialMenu( int nPlayerSlot, RadialMenuTypes_t menuType )
  1547. {
  1548. char szTarget[ MAX_PATH ] = { 0 };
  1549. CBaseEntity *pTargetEntity = NULL;
  1550. // Get our local target
  1551. C_Portal_Player *pPlayer = ToPortalPlayer( C_BasePlayer::GetLocalPlayer( nPlayerSlot ) );
  1552. if ( IsCurrentMenuTypeDisabled( pPlayer, menuType ) )
  1553. {
  1554. return false;
  1555. }
  1556. int nTeamSlot = ( pPlayer->GetTeamNumber() == TEAM_BLUE ? 0 : 1 );
  1557. float *pfLastPingTime = &( CRadialMenu::m_fLastPingTime[ GET_ACTIVE_SPLITSCREEN_SLOT() ][ nTeamSlot ] );
  1558. int *pfNumPings = &( CRadialMenu::m_nNumPings[ GET_ACTIVE_SPLITSCREEN_SLOT() ][ nTeamSlot ] );
  1559. if ( menuType == MENU_TAUNT )
  1560. {
  1561. if ( pPlayer->PredictedAirTimeEnd() > 0.85f )
  1562. {
  1563. engine->ClientCmd( "taunt" );
  1564. IGameEvent *event = gameeventmanager->CreateEvent( "player_gesture" );
  1565. if ( event )
  1566. {
  1567. event->SetInt( "userid", pPlayer->GetUserID() );
  1568. event->SetBool( "air", true );
  1569. gameeventmanager->FireEventClientSide( event );
  1570. }
  1571. return false;
  1572. }
  1573. }
  1574. else
  1575. {
  1576. float fNextPingTime = *pfLastPingTime + PING_DELAY_BASE + PING_DELAY_INCREMENT * *pfNumPings;
  1577. float fPastNextPingTime = gpGlobals->curtime - fNextPingTime;
  1578. if ( gpGlobals->curtime > *pfLastPingTime && fPastNextPingTime < 0.0f )
  1579. {
  1580. // They've been spamming pings, don't let them place another for now
  1581. return false;
  1582. }
  1583. }
  1584. // Clear this out for safety
  1585. Vector vPosition = vec3_invalid;
  1586. Vector vNormal = vec3_invalid;
  1587. if ( menuType != MENU_TAUNT )
  1588. {
  1589. // Find what's under the cursor
  1590. Vector vForward;
  1591. pPlayer->EyeVectors( &vForward );
  1592. Vector vEyeTrace = pPlayer->EyePosition() + ( vForward * MAX_TRACE_LENGTH );
  1593. Ray_t ray;
  1594. ray.Init( pPlayer->EyePosition(), vEyeTrace );
  1595. float flTheNumberOne = 1.0f;
  1596. C_Portal_Base2D *pHitPortal = UTIL_Portal_FirstAlongRay( ray, flTheNumberOne );
  1597. C_Prop_Portal *pPropPoral = dynamic_cast<C_Prop_Portal *>(pHitPortal);
  1598. trace_t tr;
  1599. // Do a trace that respects portals (allows for portal-linked doors)
  1600. CTraceFilterNoPlayers filter1;
  1601. CTraceFilterSkipTwoEntities filter2( GetPlayerHeldEntity( pPlayer ), pPlayer->GetAttachedObject() );
  1602. CTraceFilterChain filter( &filter1, &filter2 );
  1603. UTIL_Portal_TraceRay( ray, (MASK_OPAQUE_AND_NPCS|CONTENTS_SLIME), &filter, &tr );
  1604. pPlayer->CreatePingPointer( tr.endpos );
  1605. // Did we hit a portal?
  1606. if ( pHitPortal && pHitPortal->IsActivedAndLinked() && pPropPoral )
  1607. {
  1608. V_snprintf( szTarget, sizeof(szTarget), "Portal.%s", pHitPortal->m_bIsPortal2 ? "Orange" : "Blue" );
  1609. pTargetEntity = pHitPortal;
  1610. }
  1611. else
  1612. {
  1613. // See if we passed through a tractor bream
  1614. Ray_t ray;
  1615. if ( !( ( tr.DidHitWorld() || ( tr.m_pEnt && tr.m_pEnt->IsBrushModel() ) ) && !( tr.contents & CONTENTS_SLIME ) && !IsNoPortalMaterial( tr ) ) )
  1616. {
  1617. // It's not a portal surface, so maybe they wanted to hit the bridge or tbeam
  1618. for ( int i = 0; i < ITriggerTractorBeamAutoList::AutoList().Count(); ++i )
  1619. {
  1620. C_Trigger_TractorBeam *pTractorBeam = static_cast< C_Trigger_TractorBeam* >( ITriggerTractorBeamAutoList::AutoList()[ i ] );
  1621. ray.Init( tr.startpos, tr.endpos );
  1622. trace_t trTemp;
  1623. enginetrace->ClipRayToEntity( ray, MASK_SHOT, pTractorBeam, &trTemp );
  1624. if ( !trTemp.startsolid && ( trTemp.fraction < 1.0f || trTemp.m_pEnt == pTractorBeam ) )
  1625. {
  1626. tr = trTemp;
  1627. tr.m_pEnt = ClientEntityList().GetBaseEntity( 0 );
  1628. tr.surface.flags |= SURF_NOPORTAL;
  1629. // Fix up the surface normal and ping position
  1630. Vector vPointOnPath;
  1631. CalcClosestPointOnLineSegment( tr.endpos, pTractorBeam->GetStartPoint(), pTractorBeam->GetEndPoint(), vPointOnPath, NULL );
  1632. tr.plane.normal = tr.endpos - vPointOnPath;
  1633. VectorNormalize( tr.plane.normal );
  1634. tr.endpos = vPointOnPath + tr.plane.normal * pTractorBeam->GetBeamRadius();
  1635. }
  1636. }
  1637. // See if we passed through a light bridge
  1638. for ( int i = 0; i < IProjectedWallEntityAutoList::AutoList().Count(); ++i )
  1639. {
  1640. C_ProjectedWallEntity *pLightBridge = static_cast< C_ProjectedWallEntity* >( IProjectedWallEntityAutoList::AutoList()[ i ] );
  1641. Vector vBridgeUp = pLightBridge->Up();
  1642. if ( vBridgeUp.z > -0.4f && vBridgeUp.z < 0.4f )
  1643. {
  1644. // Don't hit wall bridges
  1645. continue;
  1646. }
  1647. ray.Init( tr.startpos, tr.endpos );
  1648. trace_t trTemp;
  1649. enginetrace->ClipRayToEntity( ray, MASK_SHOT, pLightBridge, &trTemp );
  1650. if ( trTemp.fraction < 1.0f || trTemp.m_pEnt == pLightBridge )
  1651. {
  1652. tr = trTemp;
  1653. tr.m_pEnt = ClientEntityList().GetBaseEntity( 0 );
  1654. tr.surface.flags |= SURF_NOPORTAL;
  1655. if ( tr.plane.normal.z < -0.9f )
  1656. {
  1657. // Point down at the bridge from above so we can tell players to stand on it even when pointing from below
  1658. tr.plane.normal.z = -tr.plane.normal.z;
  1659. tr.endpos.z += 2.0f;
  1660. }
  1661. }
  1662. }
  1663. }
  1664. // If it's an entity, just return that
  1665. if ( tr.m_pEnt && tr.DidHitNonWorldEntity() && !tr.m_pEnt->IsBrushModel() )
  1666. {
  1667. // Fill out the details
  1668. const char *lpszSignifier = tr.m_pEnt->GetSignifierName();
  1669. Assert( lpszSignifier != NULL );
  1670. V_snprintf( szTarget, sizeof(szTarget), "Entity.%s", lpszSignifier );
  1671. // Initially, use this as the target entity
  1672. pTargetEntity = tr.m_pEnt;
  1673. if ( pTargetEntity && V_strstr( szTarget, "button" ) )
  1674. {
  1675. Vector vecBoundsMax, vecBoundsMin;
  1676. pTargetEntity->GetRenderBounds( vecBoundsMin, vecBoundsMax );
  1677. vPosition = pTargetEntity->WorldSpaceCenter();
  1678. vPosition.z += (vecBoundsMax.z - 6);
  1679. vNormal = Vector( 0, 0, 1 );
  1680. }
  1681. // Access the entity interface methods to get extra data
  1682. ISignifierTarget *pSignifier = dynamic_cast<ISignifierTarget *>(tr.m_pEnt);
  1683. if ( pSignifier != NULL )
  1684. {
  1685. // See if we're overriding our hit position
  1686. if ( pSignifier->OverrideSignifierPosition() )
  1687. {
  1688. pTargetEntity = NULL;
  1689. pSignifier->GetSignifierPosition( tr.endpos, vPosition, vNormal );
  1690. }
  1691. }
  1692. }
  1693. else if ( tr.DidHitWorld() || (tr.m_pEnt && tr.m_pEnt->IsBrushModel()) )
  1694. {
  1695. // We're going to use this position explicitly
  1696. vPosition = tr.endpos;
  1697. vNormal = tr.plane.normal;
  1698. if ( tr.contents & CONTENTS_SLIME )
  1699. {
  1700. V_strncpy( szTarget, "World.Slime", sizeof(szTarget) );
  1701. }
  1702. else
  1703. {
  1704. if ( IsNoPortalMaterial( tr ) )
  1705. {
  1706. V_strncpy( szTarget, "World.NoPortal_", sizeof(szTarget) );
  1707. }
  1708. else
  1709. {
  1710. V_strncpy( szTarget, "World.", sizeof(szTarget) );
  1711. }
  1712. // If it's the world, then classify it
  1713. if ( tr.plane.normal[2] > 0.75f )
  1714. {
  1715. V_strncat( szTarget, "Floor", sizeof(szTarget) );
  1716. }
  1717. else if ( tr.plane.normal[2] < -0.75f )
  1718. {
  1719. V_strncat( szTarget, "Ceiling", sizeof(szTarget) );
  1720. }
  1721. else
  1722. {
  1723. V_strncat( szTarget, "Wall", sizeof(szTarget) );
  1724. }
  1725. }
  1726. }
  1727. else
  1728. {
  1729. // Unknown entity type!
  1730. Assert( 0 );
  1731. return false;
  1732. }
  1733. }
  1734. }
  1735. // Launch the real menu
  1736. OpenRadialMenu( szTarget, pTargetEntity, vPosition, vNormal, menuType );
  1737. return true;
  1738. }
  1739. //--------------------------------------------------------------------------------------------------------
  1740. bool IsRadialMenuOpen( void )
  1741. {
  1742. // Determine whether this window is visible or not
  1743. CRadialMenu *pRadialMenu = GET_HUDELEMENT( CRadialMenu );
  1744. if ( pRadialMenu )
  1745. {
  1746. bool isOpen = pRadialMenu->IsVisible() && !pRadialMenu->IsFading();
  1747. return isOpen;
  1748. }
  1749. return false;
  1750. }
  1751. //--------------------------------------------------------------------------------------------------------
  1752. bool OpenRadialMenuCommand( RadialMenuTypes_t menuType )
  1753. {
  1754. if ( !g_pGameRules )
  1755. return false;
  1756. if ( menuType != MENU_PLAYTEST && !g_pGameRules->IsMultiplayer() )
  1757. return false;
  1758. ASSERT_LOCAL_PLAYER_RESOLVABLE();
  1759. int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
  1760. if ( s_mouseMenuKeyHeld[ nSlot ] )
  1761. return true;
  1762. bool bSuccess = LaunchRadialMenu( nSlot, menuType );
  1763. if ( bSuccess )
  1764. {
  1765. s_mouseMenuKeyHeld[ nSlot ] = true;
  1766. }
  1767. return bSuccess;
  1768. }
  1769. void openradialmenu( const CCommand &args )
  1770. {
  1771. OpenRadialMenuCommand( MENU_PING );
  1772. }
  1773. static ConCommand mouse_menu_open( "+mouse_menu", openradialmenu, "Opens a menu while held" );
  1774. void openradialmenutaunt( const CCommand &args )
  1775. {
  1776. OpenRadialMenuCommand( MENU_TAUNT );
  1777. }
  1778. static ConCommand mouse_menu_taunt_open( "+mouse_menu_taunt", openradialmenutaunt, "Opens a menu while held" );
  1779. void openradialmenuplaytest( const CCommand &args )
  1780. {
  1781. OpenRadialMenuCommand( MENU_PLAYTEST );
  1782. }
  1783. static ConCommand mouse_menu_playtest_open( "+mouse_menu_playtest", openradialmenuplaytest, "Opens a menu while held" );
  1784. //--------------------------------------------------------------------------------------------------------
  1785. void CloseRadialMenuCommand( RadialMenuTypes_t menuType, bool bForceClose /*= false*/ )
  1786. {
  1787. if ( menuType != MENU_PLAYTEST && ( !g_pGameRules || !g_pGameRules->IsMultiplayer() ) )
  1788. return;
  1789. ASSERT_LOCAL_PLAYER_RESOLVABLE();
  1790. int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
  1791. CRadialMenu *pRadialMenu = GET_HUDELEMENT( CRadialMenu );
  1792. if ( !pRadialMenu )
  1793. return;
  1794. // Get our local target
  1795. C_Portal_Player *pPlayer = ToPortalPlayer( C_BasePlayer::GetLocalPlayer( nSlot ) );
  1796. if ( pPlayer )
  1797. pPlayer->DestroyPingPointer();
  1798. s_mouseMenuKeyHeld[ nSlot ] = false;
  1799. if ( !cl_fastradial.GetBool() )
  1800. {
  1801. return;
  1802. }
  1803. bool wasOpen = pRadialMenu->IsVisible() && !pRadialMenu->IsFading();
  1804. if ( wasOpen || bForceClose )
  1805. {
  1806. pRadialMenu->ChooseArmedButton();
  1807. int wide, tall;
  1808. pRadialMenu->GetSize( wide, tall );
  1809. wide /= 2;
  1810. tall /= 2;
  1811. pRadialMenu->LocalToScreen( wide, tall );
  1812. vgui::surface()->SurfaceSetCursorPos( wide, tall );
  1813. if ( bForceClose )
  1814. {
  1815. ShowRadialMenuPanel( false );
  1816. pRadialMenu->SetRadialMenuEnabled( false );
  1817. }
  1818. if ( !bForceClose )
  1819. {
  1820. input->Joystick_ForceRecentering( 0 );
  1821. input->Joystick_ForceRecentering( 1 );
  1822. }
  1823. }
  1824. else if ( !pRadialMenu->IsVisible() )
  1825. {
  1826. ShowRadialMenuPanel( false );
  1827. }
  1828. s_radialMenuName[ nSlot ][0] = 0;
  1829. }
  1830. void closeradialmenu( const CCommand &args )
  1831. {
  1832. CloseRadialMenuCommand( MENU_PING );
  1833. }
  1834. static ConCommand mouse_menu_close( "-mouse_menu", closeradialmenu, "Executes the highlighted button on the radial menu (if cl_fastradial is 1)" );
  1835. void closeradialmenutaunt( const CCommand &args )
  1836. {
  1837. CloseRadialMenuCommand( MENU_TAUNT );
  1838. }
  1839. static ConCommand mouse_menu_taunt_close( "-mouse_menu_taunt", closeradialmenutaunt, "Executes the highlighted button on the radial menu (if cl_fastradial is 1)" );
  1840. void closeradialmenuplaytest( const CCommand &args )
  1841. {
  1842. CloseRadialMenuCommand( MENU_PLAYTEST );
  1843. }
  1844. static ConCommand mouse_menu_playtest_close( "-mouse_menu_playtest", closeradialmenuplaytest, "Executes the highlighted button on the radial menu (if cl_fastradial is 1)" );
  1845. extern bool UTIL_EntityBoundsToSizes( C_BaseEntity *pTarget, int *pMinX, int *pMinY, int *pMaxX, int *pMaxY );
  1846. extern bool UTIL_WorldSpaceToScreensSpaceBounds( const Vector &vecCenter, const Vector &mins, const Vector &maxs, Vector2D *pMins, Vector2D *pMaxs );
  1847. void cc_quickping( const CCommand &args )
  1848. {
  1849. if ( !g_pGameRules || !g_pGameRules->IsMultiplayer() )
  1850. return;
  1851. if ( !OpenRadialMenuCommand( MENU_PING ) )
  1852. {
  1853. return;
  1854. }
  1855. CRadialMenu *pRadialMenu = GET_HUDELEMENT( CRadialMenu );
  1856. if ( pRadialMenu )
  1857. {
  1858. pRadialMenu->SetArmedButtonDir( CRadialMenu::CENTER );
  1859. pRadialMenu->SetQuickPingForceClose( true );
  1860. }
  1861. else
  1862. {
  1863. CloseRadialMenuCommand( MENU_PING, true );
  1864. }
  1865. }
  1866. static ConCommand quick_ping("+quick_ping", cc_quickping, "Ping the center option from the ping menu.");
  1867. void closequickping( const CCommand &args )
  1868. {
  1869. }
  1870. static ConCommand quick_ping_close( "-quick_ping", closequickping, "Quick ping is unpressed... nothing to do here." );
  1871. //--------------------------------------------------------------------------------------------------------
  1872. class CSignifierSystem : public CAutoGameSystemPerFrame
  1873. {
  1874. struct SignifierData_t
  1875. {
  1876. SignifierData_t( int index, int glowIndex, int playerIndex, int splitscreenID, EHANDLE hEntity, const Vector &vOrigin, float lifetime ) :
  1877. m_nLocatorIndex( index ),
  1878. m_nGlowIndex( glowIndex ),
  1879. m_nPlayerIndex( playerIndex ),
  1880. m_nSplitscreenID( splitscreenID ),
  1881. m_hTargetEntity( hEntity),
  1882. m_vOrigin( vOrigin ),
  1883. m_flLifetime( lifetime )
  1884. {
  1885. }
  1886. int m_nLocatorIndex;
  1887. int m_nGlowIndex;
  1888. EHANDLE m_hTargetEntity;
  1889. Vector m_vOrigin;
  1890. float m_flLifetime;
  1891. int m_nPlayerIndex;
  1892. int m_nSplitscreenID;
  1893. };
  1894. public:
  1895. CSignifierSystem() : CAutoGameSystemPerFrame( "CSignifierSystem" ) {}
  1896. int GetObjectScreenHeight( C_BaseEntity *pObject )
  1897. {
  1898. if ( pObject == NULL )
  1899. return 0;
  1900. int minY = 0;
  1901. int maxY = 0;
  1902. UTIL_EntityBoundsToSizes( pObject, NULL, &minY, NULL, &maxY );
  1903. return abs( maxY - minY );
  1904. }
  1905. // Pre-render
  1906. virtual void PreRender( void )
  1907. {
  1908. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
  1909. bool bDead = pPlayer && !pPlayer->IsAlive();
  1910. // Work through all our indicators and keep them up to date
  1911. FOR_EACH_VEC_BACK( m_Signifiers, itr )
  1912. {
  1913. CLocatorTarget *pLocator = Locator_GetTargetFromHandle( m_Signifiers[itr].m_nLocatorIndex );
  1914. if ( pLocator )
  1915. {
  1916. const int HEIGHT_PAD = 8;
  1917. int nPad = ( pLocator->GetIconHeight() / 2 ) + HEIGHT_PAD;
  1918. // Update our position
  1919. if ( m_Signifiers[itr].m_hTargetEntity != NULL )
  1920. {
  1921. pLocator->m_vecOrigin = m_Signifiers[itr].m_hTargetEntity->WorldSpaceCenter();
  1922. if ( V_strstr( m_Signifiers[itr].m_hTargetEntity->GetSignifierName(), "button" ) )
  1923. {
  1924. Vector vecBoundsMax, vecBoundsMin;
  1925. m_Signifiers[itr].m_hTargetEntity.Get()->GetRenderBounds( vecBoundsMin, vecBoundsMax );
  1926. pLocator->m_vecOrigin.z += vecBoundsMax.z + 42;
  1927. }
  1928. else
  1929. {
  1930. // We'd actually like to find the size of this entity on the screen and make the indicator float above the target!
  1931. int nObjectHeight = GetObjectScreenHeight( m_Signifiers[itr].m_hTargetEntity.Get() );
  1932. int nHeightOffset = ( nObjectHeight / 2 );
  1933. pLocator->m_offsetY = -(nHeightOffset+nPad);
  1934. }
  1935. }
  1936. else
  1937. {
  1938. // don't offset anymore
  1939. /*
  1940. Vector2D mins, maxs;
  1941. UTIL_WorldSpaceToScreensSpaceBounds( m_Signifiers[itr].m_vOrigin, -Vector(8,8,8), Vector(8,8,8), &mins, &maxs );
  1942. int nHeightOffset = abs(maxs.y - mins.y);
  1943. pLocator->m_offsetY = -(nHeightOffset+nPad);
  1944. */
  1945. }
  1946. pLocator->Update();
  1947. // Fade away
  1948. if ( bDead || m_Signifiers[itr].m_flLifetime < gpGlobals->curtime || ( m_Signifiers[itr].m_hTargetEntity == NULL && m_Signifiers[itr].m_vOrigin == vec3_invalid ) )
  1949. {
  1950. // Remove the glow
  1951. if ( m_Signifiers[itr].m_nGlowIndex != -1 )
  1952. {
  1953. g_GlowObjectManager.UnregisterGlowObject( m_Signifiers[itr].m_nGlowIndex );
  1954. }
  1955. Locator_RemoveTarget( m_Signifiers[itr].m_nLocatorIndex );
  1956. m_Signifiers.FastRemove( itr );
  1957. }
  1958. }
  1959. }
  1960. }
  1961. virtual void LevelShutdownPostEntity()
  1962. {
  1963. // Shut down all glows
  1964. FOR_EACH_VEC( m_Signifiers, itr )
  1965. {
  1966. if ( m_Signifiers[itr].m_nGlowIndex != -1 )
  1967. {
  1968. g_GlowObjectManager.UnregisterGlowObject( m_Signifiers[itr].m_nGlowIndex );
  1969. }
  1970. }
  1971. // Clear the list
  1972. m_Signifiers.Purge();
  1973. }
  1974. // Add an indicator to the world
  1975. void AddLocator( const char *lpszIconName, int nPlayerIndex, C_BaseEntity *pTarget, const Vector &vPosition, float flLifetime, Color rgbaColor, bool bScaleByDistance )
  1976. {
  1977. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  1978. if ( pLocalPlayer && !pLocalPlayer->IsAlive() )
  1979. {
  1980. return;
  1981. }
  1982. int nRecycledGlowIndex = -1;
  1983. // First, remove any duplicated icons
  1984. FOR_EACH_VEC_BACK( m_Signifiers, itr )
  1985. {
  1986. float flAlpha = 255;
  1987. if ( pTarget )
  1988. flAlpha = pTarget->GetRenderAlpha();
  1989. // We only want one glow handle per entity, so "steal" the glow control away from older locators
  1990. if ( m_Signifiers[itr].m_hTargetEntity && m_Signifiers[itr].m_hTargetEntity == pTarget && m_Signifiers[itr].m_nSplitscreenID == GET_ACTIVE_SPLITSCREEN_SLOT() && flAlpha > 0 )
  1991. {
  1992. nRecycledGlowIndex = m_Signifiers[itr].m_nGlowIndex;
  1993. m_Signifiers[itr].m_nGlowIndex = -1;
  1994. // Kill it
  1995. Locator_RemoveTarget( m_Signifiers[itr].m_nLocatorIndex );
  1996. m_Signifiers.FastRemove( itr );
  1997. }
  1998. else
  1999. {
  2000. CLocatorTarget *pLocator = Locator_GetTargetFromHandle( m_Signifiers[itr].m_nLocatorIndex );
  2001. if ( pLocator )
  2002. {
  2003. if ( m_Signifiers[itr].m_nPlayerIndex == nPlayerIndex && FStrEq( pLocator->GetOnscreenIconTextureName(), lpszIconName ) )
  2004. {
  2005. // Remove the glow
  2006. if ( m_Signifiers[itr].m_nGlowIndex != -1 )
  2007. {
  2008. g_GlowObjectManager.UnregisterGlowObject( m_Signifiers[itr].m_nGlowIndex );
  2009. }
  2010. // Kill it
  2011. Locator_RemoveTarget( m_Signifiers[itr].m_nLocatorIndex );
  2012. m_Signifiers.FastRemove( itr );
  2013. }
  2014. }
  2015. }
  2016. }
  2017. // Now add a fresh one
  2018. int nIndex = Locator_AddTarget();
  2019. CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( nIndex );
  2020. if ( pLocatorTarget )
  2021. {
  2022. pLocatorTarget->m_vecOrigin = ( pTarget != NULL ) ? pTarget->WorldSpaceCenter() : vPosition;
  2023. pLocatorTarget->SetOnscreenIconTextureName( lpszIconName );
  2024. pLocatorTarget->SetOffscreenIconTextureName( lpszIconName );
  2025. pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_FORCE_CAPTION ); // Draw even when occluded
  2026. if ( bScaleByDistance )
  2027. {
  2028. pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_SCALE_BY_DIST );
  2029. }
  2030. else
  2031. {
  2032. pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_SCALE_LARGE );
  2033. }
  2034. pLocatorTarget->SetIconColor( rgbaColor );
  2035. pLocatorTarget->Update();
  2036. }
  2037. // dont glow players
  2038. bool bTargetIsPlayer = ToPortalPlayer( pTarget ) != NULL;
  2039. // Now, add a glow to this entity (unless we're recycling an old one)
  2040. C_BasePlayer *pPingPlayer = UTIL_PlayerByIndex( nPlayerIndex );
  2041. bool bRecycled = ( nRecycledGlowIndex != -1 && !bTargetIsPlayer );
  2042. int nGlowIndex = bRecycled ? nRecycledGlowIndex : AddGlowToObject( pTarget, pPingPlayer ? pPingPlayer->GetTeamNumber() : 0 );
  2043. if ( bRecycled )
  2044. {
  2045. Vector vColor;
  2046. TeamPingColor( pPingPlayer ? pPingPlayer->GetTeamNumber() : 0, vColor );
  2047. g_GlowObjectManager.SetColor( nGlowIndex, vColor );
  2048. }
  2049. // Hold it
  2050. m_Signifiers.AddToTail( SignifierData_t( nIndex, nGlowIndex, nPlayerIndex, GET_ACTIVE_SPLITSCREEN_SLOT(), pTarget, vPosition, flLifetime ) );
  2051. }
  2052. CUtlVector<SignifierData_t> m_Signifiers;
  2053. };
  2054. CSignifierSystem g_SignifierSystem;
  2055. //--------------------------------------------------------------------------------------------------------
  2056. inline bool IsPortalOnFloor( CBaseEntity *pEntity )
  2057. {
  2058. C_Portal_Base2D *pHitPortal = dynamic_cast<C_Portal_Base2D *>(pEntity);
  2059. if ( pHitPortal )
  2060. {
  2061. Vector vForward;
  2062. pHitPortal->GetVectors( &vForward, NULL, NULL );
  2063. return ( vForward[2] > 0.75f );
  2064. }
  2065. return false;
  2066. }
  2067. //--------------------------------------------------------------------------------------------------------
  2068. CEG_NOINLINE void PlaceCommandTargetDecal( const Vector &vPosition, const Vector &vNormal, int iTeam, bool bJustArrows )
  2069. {
  2070. // Recreate the trace that got us here
  2071. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
  2072. if ( !pPlayer )
  2073. return;
  2074. QAngle angNormal;
  2075. VectorAngles( vNormal, angNormal );
  2076. angNormal.x += 90.0f;
  2077. Color color( 255, 255, 255 );
  2078. if ( iTeam == TEAM_RED )
  2079. color = UTIL_Portal_Color( 2, 0 ); //orange
  2080. else
  2081. color = UTIL_Portal_Color( 1, 0 ); //blue
  2082. Vector vColor;
  2083. vColor.x = color.r();
  2084. vColor.y = color.g();
  2085. vColor.z = color.b();
  2086. if ( bJustArrows )
  2087. {
  2088. DispatchParticleEffect( "command_target_ping_just_arrows", vPosition, vColor, angNormal );
  2089. }
  2090. else
  2091. {
  2092. DispatchParticleEffect( "command_target_ping", vPosition, vColor, angNormal );
  2093. }
  2094. }
  2095. CEG_PROTECT_FUNCTION( PlaceCommandTargetDecal );
  2096. //--------------------------------------------------------------------------------------------------------
  2097. void AddLocator( C_BaseEntity *pTarget, const Vector &vPosition, const Vector &vNormal, int nPlayerIndex, const char *caption, float fDisplayTime )
  2098. {
  2099. int nSplitscreenSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
  2100. bool bCountdown = StringHasPrefix( caption, "countdown" );
  2101. if ( fDisplayTime > gpGlobals->curtime )
  2102. {
  2103. if ( bCountdown )
  2104. {
  2105. IGameEvent *event = gameeventmanager->CreateEvent( "player_countdown" );
  2106. if ( event )
  2107. {
  2108. C_BasePlayer *pPlayer = UTIL_PlayerByIndex( nPlayerIndex );
  2109. event->SetInt( "userid", pPlayer ? pPlayer->GetUserID() : 0 );
  2110. gameeventmanager->FireEventClientSide( event );
  2111. }
  2112. // Count down animates
  2113. CUtlVector< SignifierInfo_t > *pSignifierQueue = GetSignifierQueue();
  2114. int nNew = pSignifierQueue->AddToTail();
  2115. SignifierInfo_t *pNewSignifier = &((*pSignifierQueue)[ nNew ]);
  2116. pNewSignifier->hTarget = pTarget;
  2117. pNewSignifier->vPos = vPosition;
  2118. pNewSignifier->vNormal = vNormal;
  2119. pNewSignifier->nPlayerIndex = nPlayerIndex;
  2120. V_strcpy( pNewSignifier->szCaption, "countdown_3" );
  2121. pNewSignifier->fDisplayTime = fDisplayTime;
  2122. nNew = pSignifierQueue->AddToTail();
  2123. pNewSignifier = &((*pSignifierQueue)[ nNew ]);
  2124. pNewSignifier->hTarget = pTarget;
  2125. pNewSignifier->vPos = vPosition;
  2126. pNewSignifier->vNormal = vNormal;
  2127. pNewSignifier->nPlayerIndex = nPlayerIndex;
  2128. V_strcpy( pNewSignifier->szCaption, "countdown_2" );
  2129. pNewSignifier->fDisplayTime = fDisplayTime + 1.0f;
  2130. nNew = pSignifierQueue->AddToTail();
  2131. pNewSignifier = &((*pSignifierQueue)[ nNew ]);
  2132. pNewSignifier->hTarget = pTarget;
  2133. pNewSignifier->vPos = vPosition;
  2134. pNewSignifier->vNormal = vNormal;
  2135. pNewSignifier->nPlayerIndex = nPlayerIndex;
  2136. V_strcpy( pNewSignifier->szCaption, "countdown_1" );
  2137. pNewSignifier->fDisplayTime = fDisplayTime + 2.0f;
  2138. nNew = pSignifierQueue->AddToTail();
  2139. pNewSignifier = &((*pSignifierQueue)[ nNew ]);
  2140. pNewSignifier->hTarget = pTarget;
  2141. pNewSignifier->vPos = vPosition;
  2142. pNewSignifier->vNormal = vNormal;
  2143. pNewSignifier->nPlayerIndex = nPlayerIndex;
  2144. V_strcpy( pNewSignifier->szCaption, "countdown_go" );
  2145. pNewSignifier->fDisplayTime = fDisplayTime + 3.0f;
  2146. }
  2147. else
  2148. {
  2149. // Put it in the queue for later display
  2150. CUtlVector< SignifierInfo_t > *pSignifierQueue = GetSignifierQueue();
  2151. int nNew = pSignifierQueue->AddToTail();
  2152. SignifierInfo_t *pNewSignifier = &((*pSignifierQueue)[ nNew ]);
  2153. pNewSignifier->hTarget = pTarget;
  2154. pNewSignifier->vPos = vPosition;
  2155. pNewSignifier->vNormal = vNormal;
  2156. pNewSignifier->nPlayerIndex = nPlayerIndex;
  2157. V_strcpy( pNewSignifier->szCaption, caption );
  2158. pNewSignifier->fDisplayTime = fDisplayTime;
  2159. }
  2160. return;
  2161. }
  2162. C_BasePlayer *pPlayer = UTIL_PlayerByIndex( nPlayerIndex );
  2163. int nTeamSlot = ( ( pPlayer && pPlayer->GetTeamNumber() == TEAM_BLUE ) ? ( 0 ) : ( 1 ) );
  2164. float *pfLastPingTime = &( CRadialMenu::m_fLastPingTime[ nSplitscreenSlot ][ nTeamSlot ] );
  2165. int *pfNumPings = &( CRadialMenu::m_nNumPings[ nSplitscreenSlot ][ nTeamSlot ] );
  2166. if ( !bCountdown )
  2167. {
  2168. // Reduce the current ping count for ones that have faded by now
  2169. float fNextPingTime = *pfLastPingTime + PING_DELAY_BASE + PING_DELAY_INCREMENT * *pfNumPings;
  2170. float fPastNextPingTime = gpGlobals->curtime - fNextPingTime;
  2171. if ( gpGlobals->curtime > *pfLastPingTime && fPastNextPingTime < 0.0f )
  2172. {
  2173. // They've been spamming pings, don't let them place another for now
  2174. return;
  2175. }
  2176. else if ( *pfNumPings > 0 )
  2177. {
  2178. // Pop off old pings that last about 2.5 seconds
  2179. float fOldestPingFadeTime = *pfLastPingTime + 2.5f;
  2180. for ( int i = 1; i <= *pfNumPings; ++i )
  2181. {
  2182. fOldestPingFadeTime -= i * PING_DELAY_INCREMENT;
  2183. }
  2184. float fTimePastOldestFaded = gpGlobals->curtime - fOldestPingFadeTime;
  2185. while ( *pfNumPings > 0 && fTimePastOldestFaded > 0.0f )
  2186. {
  2187. fTimePastOldestFaded -= *pfNumPings * PING_DELAY_INCREMENT;
  2188. (*pfNumPings)--;
  2189. }
  2190. }
  2191. }
  2192. *pfLastPingTime = gpGlobals->curtime;
  2193. (*pfNumPings)++;
  2194. const char *lpszCommand = caption;
  2195. char szIconName[MAX_PATH];
  2196. bool bAddDecal = false;
  2197. bool bColor = true;
  2198. bool bScaleByDistance = true;
  2199. char szSound[ 32 ];
  2200. V_strncpy( szSound, PING_SOUND_NAME, sizeof( szSound ) );
  2201. if ( FStrEq( lpszCommand, "look" ) )
  2202. {
  2203. V_strncpy( szIconName, "icon_look", sizeof(szIconName) );
  2204. bAddDecal = true;
  2205. }
  2206. else if ( FStrEq( lpszCommand, "death_blue" ) )
  2207. {
  2208. V_strncpy( szIconName, "icon_death_blue", sizeof(szIconName) );
  2209. bColor = false;
  2210. bScaleByDistance = false;
  2211. szSound[ 0 ] = '\0';
  2212. }
  2213. else if ( FStrEq( lpszCommand, "death_orange" ) )
  2214. {
  2215. V_strncpy( szIconName, "icon_death_orange", sizeof(szIconName) );
  2216. bColor = false;
  2217. bScaleByDistance = false;
  2218. szSound[ 0 ] = '\0';
  2219. }
  2220. else if ( FStrEq( lpszCommand, "countdown_3" ) )
  2221. {
  2222. V_strncpy( szIconName, "icon_countdown_3", sizeof(szIconName) );
  2223. bScaleByDistance = false;
  2224. V_strncpy( szSound, PING_SOUND_NAME_LOW, sizeof( szSound ) );
  2225. }
  2226. else if ( FStrEq( lpszCommand, "countdown_2" ) )
  2227. {
  2228. V_strncpy( szIconName, "icon_countdown_2", sizeof(szIconName) );
  2229. bScaleByDistance = false;
  2230. V_strncpy( szSound, PING_SOUND_NAME_LOW, sizeof( szSound ) );
  2231. }
  2232. else if ( FStrEq( lpszCommand, "countdown_1" ) )
  2233. {
  2234. V_strncpy( szIconName, "icon_countdown_1", sizeof(szIconName) );
  2235. bScaleByDistance = false;
  2236. V_strncpy( szSound, PING_SOUND_NAME_LOW, sizeof( szSound ) );
  2237. }
  2238. else if ( FStrEq( lpszCommand, "countdown_go" ) )
  2239. {
  2240. V_strncpy( szIconName, "icon_countdown_go", sizeof(szIconName) );
  2241. bScaleByDistance = false;
  2242. V_strncpy( szSound, PING_SOUND_NAME_HIGH, sizeof( szSound ) );
  2243. }
  2244. else if ( FStrEq( lpszCommand, "move_here" ) )
  2245. {
  2246. V_strncpy( szIconName, "icon_move_here", sizeof(szIconName) );
  2247. bAddDecal = true;
  2248. }
  2249. else if ( FStrEq( lpszCommand, "portal_place_floor" ) )
  2250. {
  2251. V_strncpy( szIconName, "icon_portal_place_floor", sizeof(szIconName) );
  2252. bAddDecal = true;
  2253. }
  2254. else if ( FStrEq( lpszCommand, "portal_place_wall" ) )
  2255. {
  2256. V_strncpy( szIconName, "icon_portal_place_wall", sizeof(szIconName) );
  2257. bAddDecal = true;
  2258. }
  2259. else if ( FStrEq( lpszCommand, "portal_move" ) )
  2260. {
  2261. V_strncpy( szIconName, "icon_portal_move", sizeof(szIconName) );
  2262. }
  2263. else if ( FStrEq( lpszCommand, "portal_enter" ) )
  2264. {
  2265. if ( IsPortalOnFloor( pTarget ) )
  2266. {
  2267. V_strncpy( szIconName, "icon_portal_enter_floor", sizeof(szIconName) );
  2268. }
  2269. else
  2270. {
  2271. V_strncpy( szIconName, "icon_portal_enter_wall", sizeof(szIconName) );
  2272. }
  2273. }
  2274. else if ( FStrEq( lpszCommand, "portal_exit" ) )
  2275. {
  2276. if ( IsPortalOnFloor( pTarget ) )
  2277. {
  2278. V_strncpy( szIconName, "icon_portal_exit_floor", sizeof(szIconName) );
  2279. }
  2280. else
  2281. {
  2282. V_strncpy( szIconName, "icon_portal_exit_wall", sizeof(szIconName) );
  2283. }
  2284. }
  2285. else if ( FStrEq( lpszCommand, "slime_no_drink" ) )
  2286. {
  2287. V_strncpy( szIconName, "icon_slime_no_drink", sizeof(szIconName) );
  2288. }
  2289. else if ( FStrEq( lpszCommand, "box_pickup" ) )
  2290. {
  2291. V_strncpy( szIconName, "icon_box_pickup", sizeof(szIconName) );
  2292. }
  2293. else if ( FStrEq( lpszCommand, "box_putdown" ) )
  2294. {
  2295. V_strncpy( szIconName, "icon_box_drop", sizeof(szIconName) );
  2296. bAddDecal = true;
  2297. }
  2298. else if ( FStrEq( lpszCommand, "button_press" ) )
  2299. {
  2300. V_strncpy( szIconName, "icon_button_press", sizeof(szIconName) );
  2301. }
  2302. else if ( FStrEq( lpszCommand, "button_tall_press" ) )
  2303. {
  2304. V_strncpy( szIconName, "icon_button_tall_press", sizeof(szIconName) );
  2305. }
  2306. else if ( FStrEq( lpszCommand, "turret_warning" ) )
  2307. {
  2308. V_strncpy( szIconName, "icon_turret_warning", sizeof(szIconName) );
  2309. }
  2310. else if ( FStrEq( lpszCommand, "love_it" ) )
  2311. {
  2312. V_strncpy( szIconName, "icon_love_it", sizeof( szIconName ) );
  2313. bAddDecal = true;
  2314. }
  2315. else if ( FStrEq( lpszCommand, "stuck" ) )
  2316. {
  2317. V_strncpy( szIconName, "icon_stuck", sizeof( szIconName ) );
  2318. bAddDecal = true;
  2319. }
  2320. else if ( FStrEq( lpszCommand, "hate_it" ) )
  2321. {
  2322. V_strncpy( szIconName, "icon_hate_it", sizeof( szIconName ) );
  2323. bAddDecal = true;
  2324. }
  2325. else if ( FStrEq( lpszCommand, "confused" ) )
  2326. {
  2327. V_strncpy( szIconName, "icon_confused", sizeof( szIconName ) );
  2328. bAddDecal = true;
  2329. }
  2330. else if ( FStrEq( lpszCommand, "done" ) )
  2331. {
  2332. return;
  2333. }
  2334. else
  2335. {
  2336. // Found an unknown command!
  2337. Assert(0);
  2338. return;
  2339. }
  2340. C_Team *pTeam = pPlayer ? pPlayer->GetTeam() : NULL;
  2341. // Add a decal down where we pointed
  2342. if ( bAddDecal )
  2343. {
  2344. bool bJustArrows = false;
  2345. if ( pTarget && V_strstr( pTarget->GetSignifierName(), "button" ) )
  2346. {
  2347. bJustArrows = true;
  2348. }
  2349. int iTeam = pTeam ? pTeam->GetTeamNumber() : 0;
  2350. PlaceCommandTargetDecal( vPosition, vNormal, iTeam, bJustArrows );
  2351. }
  2352. Color color( 255, 255, 255, 255 );
  2353. if ( pPlayer )
  2354. {
  2355. if ( szSound[ 0 ] != '\0' )
  2356. {
  2357. pPlayer->EmitSound( szSound );
  2358. }
  2359. if ( bColor )
  2360. {
  2361. if ( pTeam && pTeam->GetTeamNumber() == TEAM_RED )
  2362. {
  2363. color = UTIL_Portal_Color( 2, 0 ); //orange
  2364. }
  2365. else
  2366. {
  2367. color = UTIL_Portal_Color( 1, 0 ); //blue
  2368. }
  2369. }
  2370. }
  2371. // if single player playtest, our alpha will come back 0, push it up
  2372. if ( color.a() == 0 )
  2373. {
  2374. color.SetColor( color.r(), color.g(), color.b(), 255 );
  2375. }
  2376. float flLifetime = gpGlobals->curtime + ( bCountdown ? 1.0f : 3.0f );
  2377. Vector vecSigPos = vPosition;
  2378. vecSigPos += vNormal*64;
  2379. g_SignifierSystem.AddLocator( szIconName, nPlayerIndex, pTarget, vecSigPos, flLifetime, color, bScaleByDistance );
  2380. }
  2381. //--------------------------------------------------------------------------------------------------------
  2382. static void __MsgFunc_AddLocator( bf_read &msg )
  2383. {
  2384. // Find the index of the sending player
  2385. int nPlayerIndex = msg.ReadShort();
  2386. // Find the entity in question
  2387. C_BaseEntity *pTarget = UTIL_EntityFromUserMessageEHandle( msg.ReadLong() );
  2388. float fDisplayTime = msg.ReadFloat();
  2389. Vector vPosition = vec3_invalid;
  2390. Vector vNormal = vec3_invalid;
  2391. msg.ReadBitVec3Coord( vPosition );
  2392. msg.ReadBitVec3Normal( vNormal );
  2393. // Find the name of the icon to show
  2394. char iconName[2048];
  2395. msg.ReadString( iconName, sizeof(iconName) );
  2396. AddLocator( pTarget, vPosition, vNormal, nPlayerIndex, iconName, fDisplayTime );
  2397. }
  2398. USER_MESSAGE_REGISTER( AddLocator );