|
|
//===== Copyright Valve Corporation, All rights reserved. ======//
//
// Radial, context-sensitive menu for co-op communication
//
//================================================================//
#include "cbase.h"
#include <string.h>
#include <stdio.h>
#include <vgui_controls/ImagePanel.h>
#include "voice_status.h"
#include "c_playerresource.h"
#include "cliententitylist.h"
#include "c_baseplayer.h"
#include "materialsystem/imesh.h"
#include "view.h"
#include "materialsystem/imaterial.h"
#include "tier0/dbg.h"
#include "cdll_int.h"
#include "menu.h" // for chudmenu defs
#include "keyvalues.h"
#include <filesystem.h>
#include "c_team.h"
#include "vgui/isurface.h"
#include "iclientmode.h"
#include "c_portal_player.h"
#include "hud_locator_target.h"
#include "c_user_message_register.h"
#include "portal_placement.h"
#include "glow_outline_effect.h"
#include "soundemittersystem/isoundemittersystembase.h"
#include "c_prop_portal.h"
#include "c_trigger_tractorbeam.h"
#include "c_projectedwallentity.h"
#include "portal_mp_gamerules.h"
#include "vgui/cursor.h"
#include "fmtstr.h"
#include "vgui_int.h"
#include "vgui/IVgui.h"
#include <game/client/iviewport.h>
#include "radialmenu.h"
#include "radialmenu_taunt.h"
#include "radialbutton.h"
#include "cegclientwrapper.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern void AddLocator( C_BaseEntity *pTarget, const Vector &vPosition, const Vector &vNormal, int nPlayerIndex, const char *caption, float fDisplayTime );
ConVar cl_fastradial( "cl_fastradial", "1", FCVAR_DEVELOPMENTONLY, "If 1, releasing the button on a radial menu executes the highlighted button" ); ConVar RadialMenuDebug( "cl_rosette_debug", "0" ); ConVar cl_rosette_line_inner_radius( "cl_rosette_line_inner_radius", "25" ); ConVar cl_rosette_line_outer_radius( "cl_rosette_line_outer_radius", "45" ); ConVar cl_rosette_gamepad_lockin_time( "cl_rosette_gamepad_lockin_time", "0.2" ); ConVar cl_rosette_gamepad_expand_time( "cl_rosette_gamepad_expand_time", "0.5" );
#if defined( PORTAL2_PUZZLEMAKER )
extern ConVar sv_record_playtest; #endif // PORTAL2_PUZZLEMAKER
#define PING_DELAY_BASE 0.05f
#define PING_DELAY_INCREMENT 0.05f
#define PING_SOUND_NAME "GameUI.UiCoopHudClick"
#define PING_SOUND_NAME_LOW "GameUI.UiCoopHudClickLow"
#define PING_SOUND_NAME_HIGH "GameUI.UiCoopHudClickHigh"
#define RADIAL_MENU_POINTER_TEXTURE "vgui/hud/icon_arrow_right"
void FlushClientMenus( void ); void CloseRadialMenuCommand( RadialMenuTypes_t menuType, bool bForceClose = false );
static ClientMenuManager TheClientMenuManager; static ClientMenuManagerPlaytest TheClientMenuManagerPlaytest;
//--------------------------------------------------------------------------------------------------------
static char s_radialMenuName[ MAX_SPLITSCREEN_PLAYERS ][ 64 ]; static bool s_mouseMenuKeyHeld[ MAX_SPLITSCREEN_PLAYERS ];
// Precache our glow effects
PRECACHE_REGISTER_BEGIN( GLOBAL, PrecacheRadialMenu ) PRECACHE( MATERIAL, "dev/glow_blur_x" ) PRECACHE( MATERIAL, "dev/glow_blur_y" ) PRECACHE( MATERIAL, "dev/glow_color" ) PRECACHE( MATERIAL, "dev/glow_downsample" ) PRECACHE( MATERIAL, "dev/halo_add_to_screen" ) PRECACHE( MATERIAL, RADIAL_MENU_POINTER_TEXTURE ) PRECACHE_REGISTER_END()
#define GLOW_OUTLINE_COLOR Vector( 0.25f, 0.62f, 1.0f )
#define GLOW_OUTLINE_ALPHA 0.75f
struct SignifierInfo_t { EHANDLE hTarget; Vector vPos; Vector vNormal; int nPlayerIndex; char szCaption[ 32 ]; float fDisplayTime; };
CUtlVector< SignifierInfo_t > g_SignifierQueue[ MAX_SPLITSCREEN_PLAYERS ];
CUtlVector< SignifierInfo_t >* GetSignifierQueue( void ) { ASSERT_LOCAL_PLAYER_RESOLVABLE(); return &(g_SignifierQueue[ GET_ACTIVE_SPLITSCREEN_SLOT() ]); }
void TeamPingColor( int nTeamNumber, Vector &vColor ) { Color color = Color( GLOW_OUTLINE_COLOR[0], GLOW_OUTLINE_COLOR[1], GLOW_OUTLINE_COLOR[2] ); if ( nTeamNumber == TEAM_RED ) { color = UTIL_Portal_Color( 2, 0 ); //orange
} else { color = UTIL_Portal_Color( 1, 0 ); //blue
}
vColor.x = (float)color.r()/255.0f; vColor.y = (float)color.g()/255.0f; vColor.z = (float)color.b()/255.0f; }
//--------------------------------------------------------------------------------------------------------
int AddGlowToObject( C_BaseEntity *pObject, int nTeamNumber ) { if ( pObject == NULL || pObject->GetRenderAlpha() <= 0 ) return -1;
// Determine if this entity uses the glow capability
ISignifierTarget *pSignifier = dynamic_cast<ISignifierTarget *>(pObject); if ( pSignifier != NULL ) { if ( pSignifier->UseSelectionGlow() == false ) return -1; }
Vector vColor; TeamPingColor( nTeamNumber, vColor );
return g_GlowObjectManager.RegisterGlowObject( pObject, vColor, GLOW_OUTLINE_ALPHA, GET_ACTIVE_SPLITSCREEN_SLOT() ); }
void RadialMenuMouseCallback( uint8* pData, size_t iSize ) { // make sure we're getting as much data as we expect
Assert( iSize == sizeof(int)*2 ); // check for null pointer
if ( pData == NULL ) return;
// capture the data
int nCursorX = ((int*)pData)[0]; int nCursorY = ((int*)pData)[1]; // get the radial menu and give it the cursor position
CRadialMenu *pRadialMenu = GET_HUDELEMENT( CRadialMenu ); if ( pRadialMenu ) { pRadialMenu->SetDemoCursorPos( nCursorX, nCursorY ); } }
void ClientMenuManager::AddMenuFile( const char *filename ) { if ( !m_menuKeys ) { m_menuKeys = new KeyValues( "ClientMenu" ); }
KeyValues *data = new KeyValues( "ClientMenu" ); if ( !data->LoadFromFile( filesystem, filename ) ) { Warning( "Could not load client menu %s\n", filename ); data->deleteThis(); return; }
KeyValues *menuKey = data->GetFirstTrueSubKey(); while ( menuKey ) { if ( m_menuKeys->FindKey( menuKey->GetName(), false ) == NULL ) { data->RemoveSubKey( menuKey ); m_menuKeys->AddSubKey( menuKey ); } menuKey = data->GetFirstTrueSubKey(); } data->deleteThis(); }
KeyValues *ClientMenuManager::FindMenu( const char *menuName ) { // do not show the menu if the player is dead or is an observer
C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); if ( !player ) return NULL;
// Find our menu, honoring Alive/Dead/Team suffixes
/*
const char *teamSuffix = player->GetTeam()->Get_Name(); const char *lifeSuffix = player->IsAlive() ? "Alive" : "Dead";
CFmtStr str; const char *fullMenuName = str.sprintf( "%s,%s,%s", menuName, teamSuffix, lifeSuffix ); */
CFmtStr str; const char *fullMenuName = str.sprintf( "%s,%s", "Command", menuName ); KeyValues *menuKey = m_menuKeys->FindKey( fullMenuName, false ); if ( !menuKey ) { fullMenuName = menuName; menuKey = m_menuKeys->FindKey( fullMenuName, false ); }
return menuKey; }
void ClientMenuManager::Flush( void ) { Reset(); AddMenuFile( "scripts/RadialMenu.txt" ); }
//-------------------------------------------------------------------------------------------------------
KeyValues *ClientMenuManagerPlaytest::FindMenu( const char *menuName ) { return ClientMenuManager::FindMenu( menuName ); }
//-------------------------------------------------------------------------------------------------------
void ClientMenuManagerPlaytest::Flush( void ) { Reset(); AddMenuFile( "scripts/RadialMenuPlaytest.txt" ); }
void ShowRadialMenuPanel( bool bShow ) { if ( IsPC() ) { GetViewPortInterface()->ShowPanel( PANEL_RADIAL_MENU, bShow ); } }
CRadialMenuPanel::CRadialMenuPanel(IViewPort *pViewPort) : BaseClass(NULL, PANEL_RADIAL_MENU) { m_pViewPort = pViewPort;
// load the new scheme early!!
SetScheme("ClientScheme"); SetMoveable(false); SetSizeable(false);
SetCursor( NULL );
LoadControlSettings("Resource/UI/RadialMenuPanel.res"); InvalidateLayout();
SetPaintBackgroundEnabled( false ); }
void CRadialMenuPanel::ShowPanel( bool bShow ) { if ( BaseClass::IsVisible() == bShow ) return;
if ( bShow ) { Activate();
SetMouseInputEnabled( true ); } else { SetVisible( false ); SetMouseInputEnabled( false ); }
m_pViewPort->ShowBackGround( false ); }
float CRadialMenu::m_fLastPingTime[ MAX_SPLITSCREEN_PLAYERS ][ 2 ] = { { 0.0f, 0.0f }, { 0.0f, 0.0f } }; int CRadialMenu::m_nNumPings[ MAX_SPLITSCREEN_PLAYERS ][ 2 ] = { { 0, 0 }, { 0, 0 } };
DECLARE_HUDELEMENT( CRadialMenu );
//--------------------------------------------------------------------------------------------------------------
CRadialMenu::CRadialMenu( const char *pElementName ) : CHudElement( pElementName ), BaseClass( NULL, "RadialMenu" ) { SetParent( GetClientMode()->GetViewport() ); SetHiddenBits( HIDEHUD_PLAYERDEAD );
MEM_ALLOC_CREDIT(); // initialize dialog
m_resource = new KeyValues( "RadialMenu" ); m_resource->LoadFromFile( filesystem, "resource/UI/RadialMenu.res" ); m_menuData = NULL; FlushClientMenus();
// load the new scheme early!!
SetScheme("ClientScheme"); SetProportional(true);
HandleControlSettings();
SetCursor( NULL );
m_nArrowTexture = -1;
m_minButtonX = 0; m_minButtonY = 0; m_maxButtonX = 0; m_maxButtonY = 0;
m_demoCursorX = m_demoCursorY = 0; m_nEntityGlowIndex = -1;
m_bEditing = false; m_bDragging = false; m_nDraggingTaunt = -1;
m_fFadeInTime = 0.0f; m_fSelectionLockInTime = 0.0f;
m_bEnabled = false; m_bQuickPingForceClose = false;
vgui::ivgui()->AddTickSignal( GetVPanel() ); }
//--------------------------------------------------------------------------------------------------------
const char *CRadialMenu::ButtonNameFromDir( ButtonDir dir ) { switch( dir ) { case CENTER: return "Center"; case NORTH: return "North"; case NORTH_EAST: return "NorthEast"; case EAST: return "East"; case SOUTH_EAST: return "SouthEast"; case SOUTH: return "South"; case SOUTH_WEST: return "SouthWest"; case WEST: return "West"; case NORTH_WEST: return "NorthWest"; }
return "None"; }
//--------------------------------------------------------------------------------------------------------
CRadialMenu::ButtonDir CRadialMenu::DirFromButtonName( const char * name ) { if ( FStrEq( name, "Center" ) ) return CENTER; if ( FStrEq( name, "North" ) ) return NORTH; if ( FStrEq( name, "NorthEast" ) ) return NORTH_EAST; if ( FStrEq( name, "East" ) ) return EAST; if ( FStrEq( name, "SouthEast" ) ) return SOUTH_EAST; if ( FStrEq( name, "South" ) ) return SOUTH; if ( FStrEq( name, "SouthWest" ) ) return SOUTH_WEST; if ( FStrEq( name, "West" ) ) return WEST; if ( FStrEq( name, "NorthWest" ) ) return NORTH_WEST;
return CENTER; }
//--------------------------------------------------------------------------------------------------------
/**
* Created controls from the resource file. We know how to make a special PolygonButton :) */ vgui::Panel *CRadialMenu::CreateControlByName( const char *controlName ) { if( !Q_stricmp( "PolygonButton", controlName ) ) { vgui::Button *newButton = new CRadialButton( this, NULL ); return newButton; } else { return BaseClass::CreateControlByName( controlName ); } }
//--------------------------------------------------------------------------------------------------------
CRadialMenu::~CRadialMenu() { m_resource->deleteThis(); if ( m_menuData ) { m_menuData->deleteThis(); }
if ( vgui::surface() && m_nArrowTexture != -1 ) { vgui::surface()->DestroyTextureID( m_nArrowTexture ); m_nArrowTexture = -1; } }
void CRadialMenu::HandleControlSettings() { LoadControlSettings("Resource/UI/RadialMenu.res");
for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { ButtonDir dir = (ButtonDir)i; const char *buttonName = ButtonNameFromDir( dir ); m_buttons[i] = dynamic_cast<CRadialButton *>(FindChildByName( buttonName )); if ( m_buttons[i] ) { m_buttons[i]->SetMouseInputEnabled( true ); } }
m_armedButtonDir = CENTER; }
void CRadialMenu::StartDrag( void ) { if ( m_nDraggingTaunt == -1 ) { return; }
m_bDragging = true;
for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { ButtonDir dir = (ButtonDir)i; const char *buttonName = ButtonNameFromDir( dir ); m_buttons[i] = dynamic_cast<CRadialButton *>(FindChildByName( buttonName )); if ( m_buttons[i] ) { m_buttons[i]->SetMouseInputEnabled( false ); } }
SetArmedButtonDir( CENTER ); }
void CRadialMenu::EndDrag( void ) { if ( !m_bDragging || m_nDraggingTaunt == -1 ) { return; }
int nSwap = -1;
for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { ButtonDir dir = (ButtonDir)i; const char *buttonName = ButtonNameFromDir( dir ); m_buttons[i] = dynamic_cast<CRadialButton *>(FindChildByName( buttonName )); if ( m_buttons[i] ) { m_buttons[i]->SetMouseInputEnabled( true );
int mouseX, mouseY; vgui::surface()->SurfaceGetCursorPos( mouseX, mouseY ); if ( nSwap == -1 && m_buttons[i]->IsVisible() && m_buttons[i]->IsEnabled() && m_buttons[i]->IsWithinTraverse( mouseX, mouseY, false ) ) { nSwap = i; } } }
if ( nSwap != -1 && nSwap != CENTER ) { CUtlVector< TauntStatusData > *pTauntData = GetClientMenuManagerTaunt().GetTauntData(); TauntStatusData *pData = &((*pTauntData)[ m_nDraggingTaunt ]);
const char *pDir = "empty";
switch ( nSwap ) { case NORTH: pDir = "North"; break;
case SOUTH: pDir = "South"; break;
case WEST: pDir = "West"; break;
case EAST: pDir = "East"; break;
case NORTH_WEST: pDir = "NorthWest"; break;
case NORTH_EAST: pDir = "NorthEast"; break;
case SOUTH_WEST: pDir = "SouthWest"; break;
case SOUTH_EAST: pDir = "SouthEast"; break; }
GetClientMenuManagerTaunt().SetTauntPosition( pData->szName, pDir ); //GetClientMenuManagerTaunt().UpdateStorageChange( pData, GetClientMenuManagerTaunt().UPDATE_STORAGE_EQUIPSLOT );
KeyValues *menuKey = GetClientMenuManagerTaunt().FindMenu( "Default" ); if ( menuKey ) { SetData( menuKey ); } }
m_bDragging = false; m_nDraggingTaunt = -1; }
//--------------------------------------------------------------------------------------------------------
/**
* The radial menu should cover the entire screen to capture mouse input, so we should have a blank * background. */ void CRadialMenu::ApplySchemeSettings( vgui::IScheme *pScheme ) { HandleControlSettings();
BaseClass::ApplySchemeSettings( pScheme ); SetBgColor( Color( 0, 0, 0, 0 ) ); m_lineColor = pScheme->GetColor( "rosette.LineColor", Color( 192, 192, 192, 128 ) );
if ( RadialMenuDebug.GetBool() ) { SetCursor( vgui::dc_crosshair ); } else { SetCursor( NULL ); }
// Restore button names/commands
if ( m_menuData ) { SetData( m_menuData ); }
SetAlpha( 0 ); }
void CRadialMenu::PerformLayout( void ) { SetRadialMenuEnabled( false );
BaseClass::PerformLayout(); }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::ShowPanel( bool show ) { if ( IsVisible() == show ) return;
if ( show ) { for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { if ( !m_buttons[i] ) continue;
m_buttons[i]->SetArmed( false ); m_buttons[i]->SetFakeArmed( false ); m_buttons[i]->SetChosen( false ); }
SetMouseInputEnabled( true ); m_cursorX = m_cursorY = 0; m_demoCursorX = m_demoCursorY = 0; m_bFirstCentering = true; } else { SetRadialMenuEnabled( false ); SetMouseInputEnabled( false ); } }
void CRadialMenu::PaintBackground( void ) { int nSlot = vgui::ipanel()->GetMessageContextId( GetVPanel() ); ACTIVE_SPLITSCREEN_PLAYER_GUARD( nSlot );
if ( m_armedButtonDir != CENTER || !m_buttons[ CENTER ]->IsVisible() ) { int x, y, wide, tall; GetBounds( x, y, wide, tall );
float fCenterX = x + wide/2; float fCenterY = y + tall/2;
int nCursorX, nCursorY; if ( !input->ControllerModeActive() ) { nCursorX = m_cursorX; nCursorY = m_cursorY; } else { float fJoyForward, fJoySide, fJoyPitch, fJoyYaw = 0.0f; input->Joystick_Querry( fJoyForward, fJoySide, fJoyPitch, fJoyYaw );
// Replace if the other stick was pushed further
// We need to use both sticks because they might have southpaw or legacy set
if ( fabsf( fJoyForward ) > fabsf( fJoyPitch ) ) { fJoyPitch = fJoyForward; } else { // Reflip it if the y was inverted because this shouldn't be inverted
static SplitScreenConVarRef s_joy_inverty( "joy_inverty" ); if ( s_joy_inverty.IsValid() && s_joy_inverty.GetBool( nSlot ) ) { fJoyPitch *= -1.0f; } }
// Replace if the other stick was pushed further
// We need to use both sticks because they might have southpaw or legacy set
if ( fabsf( fJoySide ) > fabsf( fJoyYaw ) ) { fJoyYaw = fJoySide; }
if ( fJoyPitch > 0.1f || fJoyYaw > 0.1f ) { nCursorX = fCenterX + fJoyYaw * 100.0f; nCursorY = fCenterY + fJoyPitch * 100.0f; } else { int buttonX, buttonY; m_buttons[ m_armedButtonDir ]->GetIcon()->GetPos( nCursorX, nCursorY ); m_buttons[ m_armedButtonDir ]->GetPos( buttonX, buttonY ); nCursorX += buttonX + m_buttons[ m_armedButtonDir ]->GetIcon()->GetWide() / 2; nCursorY += buttonY + m_buttons[ m_armedButtonDir ]->GetIcon()->GetTall() / 2; } }
Vector2D vNormal( nCursorX - fCenterX, nCursorY - fCenterY ); if ( vNormal.IsZero() ) { vNormal = Vector2D( 0.0f, -1.0f ); } Vector2DNormalize( vNormal ); Vector2D vDown( vNormal.y, -vNormal.x );
float innerRadius = cl_rosette_line_inner_radius.GetFloat(); float outerRadius = cl_rosette_line_outer_radius.GetFloat(); innerRadius = YRES( innerRadius ) * 0.75f; outerRadius = YRES( outerRadius );
float fSize = ( outerRadius - innerRadius ) * 0.5f; float fOffset = innerRadius + fSize;
fCenterX += vNormal.x * fOffset; fCenterY += vNormal.y * fOffset;
vgui::Vertex_t points[4] = { vgui::Vertex_t( Vector2D( fCenterX, fCenterY ) - vDown * fSize - vNormal * fSize, Vector2D(0,0) ), // Top Left
vgui::Vertex_t( Vector2D( fCenterX, fCenterY ) + vDown * fSize - vNormal * fSize, Vector2D(0,1) ), // Bottom Left
vgui::Vertex_t( Vector2D( fCenterX, fCenterY ) + vDown * fSize + vNormal * fSize, Vector2D(1,1) ), // Bottom Right
vgui::Vertex_t( Vector2D( fCenterX, fCenterY ) - vDown * fSize + vNormal * fSize, Vector2D(1,0) ) // Top Right
};
vgui::surface()->DrawSetColor( 255, 255, 255, 255 ); vgui::surface()->DrawSetTexture( m_nArrowTexture ); vgui::surface()->DrawTexturedPolygon( 4, points ); }
BaseClass::PaintBackground(); }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::Paint( void ) { int nBaseAlpha = 0; float fFade = gpGlobals->curtime - m_fFadeInTime; if ( fFade > 0.0f ) { fFade = MIN( 1.0f, fFade * 10.0f ); nBaseAlpha = fFade * 255.0f; }
const float fadeDuration = 0.1f; if ( m_fading ) { float fadeTimeRemaining = m_fadeStart + fadeDuration - gpGlobals->curtime; int alpha = nBaseAlpha * fadeTimeRemaining / fadeDuration; SetAlpha( alpha ); if ( alpha <= 0 ) { ShowRadialMenuPanel( false ); SetRadialMenuEnabled( false ); return; } } else { SetAlpha( nBaseAlpha ); }
int x, y, wide, tall; GetBounds( x, y, wide, tall ); vgui::surface()->DrawSetColor( m_lineColor );
int centerX = x + wide/2; int centerY = y + tall/2; float innerRadius = cl_rosette_line_inner_radius.GetFloat(); float outerRadius = cl_rosette_line_outer_radius.GetFloat(); innerRadius = YRES( innerRadius ); outerRadius = YRES( outerRadius );
// Draw horizontal and vertical lines
if ( m_armedButtonDir != EAST && m_buttons[EAST]->IsVisible() && m_buttons[EAST]->IsEnabled() ) { vgui::surface()->DrawLine( centerX + innerRadius, centerY, centerX + outerRadius, centerY ); } if ( m_armedButtonDir != WEST && m_buttons[WEST]->IsVisible() && m_buttons[WEST]->IsEnabled() ) { vgui::surface()->DrawLine( centerX - innerRadius, centerY, centerX - outerRadius, centerY ); } if ( m_armedButtonDir != SOUTH && m_buttons[SOUTH]->IsVisible() && m_buttons[SOUTH]->IsEnabled() ) { vgui::surface()->DrawLine( centerX, centerY + innerRadius, centerX, centerY + outerRadius ); } if ( m_armedButtonDir != NORTH && m_buttons[NORTH]->IsVisible() && m_buttons[NORTH]->IsEnabled() ) { vgui::surface()->DrawLine( centerX, centerY - innerRadius, centerX, centerY - outerRadius ); }
// Draw diagonal lines
const float scale = 0.707f; // sqrt(2) / 2
if ( m_armedButtonDir != SOUTH_EAST && m_buttons[SOUTH_EAST]->IsVisible() && m_buttons[SOUTH_EAST]->IsEnabled() ) { vgui::surface()->DrawLine( centerX + innerRadius * scale, centerY + innerRadius * scale, centerX + outerRadius * scale, centerY + outerRadius * scale ); }
if ( m_armedButtonDir != NORTH_WEST && m_buttons[NORTH_WEST]->IsVisible() && m_buttons[NORTH_WEST]->IsEnabled() ) { vgui::surface()->DrawLine( centerX - innerRadius * scale, centerY - innerRadius * scale, centerX - outerRadius * scale, centerY - outerRadius * scale ); }
if ( m_armedButtonDir != NORTH_EAST && m_buttons[NORTH_EAST]->IsVisible() && m_buttons[NORTH_EAST]->IsEnabled() ) { vgui::surface()->DrawLine( centerX + innerRadius * scale, centerY - innerRadius * scale, centerX + outerRadius * scale, centerY - outerRadius * scale ); }
if ( m_armedButtonDir != SOUTH_WEST && m_buttons[SOUTH_WEST]->IsVisible() && m_buttons[SOUTH_WEST]->IsEnabled() ) { vgui::surface()->DrawLine( centerX - innerRadius * scale, centerY + innerRadius * scale, centerX - outerRadius * scale, centerY + outerRadius * scale ); }
if ( RadialMenuDebug.GetBool() ) { vgui::surface()->DrawSetColor( m_lineColor ); vgui::surface()->DrawOutlinedRect( x + m_minButtonX, y + m_minButtonY, x + m_maxButtonX, y + m_maxButtonY );
// draw the cursor position
vgui::surface()->DrawLine( m_cursorX -10, m_cursorY, m_cursorX + 10, m_cursorY ); vgui::surface()->DrawLine( m_cursorX , m_cursorY - 10 , m_cursorX, m_cursorY + 10 ); }
BaseClass::Paint(); }
void PlayDeactivateSound() { C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); // If we've got the player and they selected a different button
if ( localPlayer ) { localPlayer->EmitSound( "GameUI.UiCoopHudDeactivate" ); } }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::OnMousePressed( vgui::MouseCode code ) { if ( m_bEditing ) { if ( code == MOUSE_LEFT ) { StartDrag(); } }
if ( code == MOUSE_RIGHT ) { StartFade(); PlayDeactivateSound(); } BaseClass::OnMousePressed( code ); }
void CRadialMenu::OnMouseReleased( vgui::MouseCode code ) { if ( m_bEditing ) { if ( code == MOUSE_LEFT ) { EndDrag(); } }
BaseClass::OnMouseReleased( code ); }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::OnCommand( const char *command ) { if ( RadialMenuDebug.GetBool() ) { Msg( "%f: Clicked on button with command '%s'\n", gpGlobals->curtime, command ); }
if ( !Q_strcmp(command, "done") ) { C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); if ( localPlayer ) { localPlayer->EmitSound("MouseMenu.abort"); } StartFade(); } else { // Undone... people hate clicking to select, so don't let them train themselves to do it
//StartFade();
//SendCommand( command );
}
BaseClass::OnCommand( command ); }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::SetArmedButtonDir( ButtonDir dir ) { if ( dir != NUM_BUTTON_DIRS ) { CRadialButton *armedButton = m_buttons[dir]; if ( m_buttons[dir]->GetPassthru() ) { armedButton = m_buttons[dir]->GetPassthru(); for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { if ( m_buttons[i] == armedButton ) { dir = (ButtonDir)i; break; } } } }
if ( m_buttons[ dir ]->IsEnabled() ) { C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); // If we've got the player and they selected a different button
if ( localPlayer && dir != m_armedButtonDir ) { if( dir != CENTER ) { localPlayer->EmitSound("GameUI.UiCoopHudFocus"); } else { localPlayer->EmitSound("GameUI.UiCoopHudUnfocus"); } } }
m_armedButtonDir = dir;
for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { if ( !m_buttons[i] ) continue;
m_buttons[i]->SetFakeArmed( false ); m_buttons[i]->SetArmed( false ); if ( i != m_armedButtonDir ) { m_buttons[i]->SetChosen( false ); } }
if ( m_armedButtonDir != NUM_BUTTON_DIRS ) { if ( m_buttons[m_armedButtonDir] ) { #ifdef OSX
if ( !m_buttons[m_armedButtonDir]->IsFakeArmed() ) { m_buttons[m_armedButtonDir]->SetArmed( true ); OnCursorEnteredButton( m_cursorX, m_cursorY, m_buttons[m_armedButtonDir] ); } #endif
m_buttons[m_armedButtonDir]->SetFakeArmed( true ); } }
if ( m_armedButtonDir != CENTER ) { SetFadeInTime( gpGlobals->curtime - 1.0f ); } }
//--------------------------------------------------------------------------------------------------------
static const char *ButtonDirString( CRadialMenu::ButtonDir dir ) { switch ( dir ) { case CRadialMenu::CENTER: return "CENTER"; case CRadialMenu::NORTH: return "NORTH"; case CRadialMenu::NORTH_EAST: return "NORTH_EAST"; case CRadialMenu::EAST: return "EAST"; case CRadialMenu::SOUTH_EAST: return "SOUTH_EAST"; case CRadialMenu::SOUTH: return "SOUTH"; case CRadialMenu::SOUTH_WEST: return "SOUTH_WEST"; case CRadialMenu::WEST: return "WEST"; case CRadialMenu::NORTH_WEST: return "NORTH_WEST"; default: return "UNKNOWN"; } }
//--------------------------------------------------------------------------------------------------------
bool IsCurrentMenuTypeDisabled( C_Portal_Player *pPlayer, RadialMenuTypes_t menuType ) { // if the player isn't valid or is in the middle of taunting
if ( !pPlayer || pPlayer->IsTaunting() ) { return true; }
// check the specific type of radial menu
switch ( menuType ) { case MENU_PING: if ( pPlayer->IsPingDisabled() ) { return true; } break; case MENU_TAUNT: if ( pPlayer->IsTauntDisabled() ) { return true; } break; case MENU_PLAYTEST: // don't allow playtest pings where normal pings aren't allowed
if ( pPlayer->IsPingDisabled() #if defined( PORTAL2_PUZZLEMAKER )
|| ( !sv_record_playtest.GetBool() && !engine->IsPlayingDemo() ) #endif // PORTAL2_PUZZLEMAKER
) { return true; } break; }
return false; }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::OnCursorEnteredButton( int x, int y, CRadialButton *button ) { ButtonDir nNewDir = NUM_BUTTON_DIRS;
for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { if ( m_buttons[i] == button ) { m_cursorX = x; m_cursorY = y; // If we've got the player and they selected a different button
if ( (ButtonDir)i != m_armedButtonDir ) { nNewDir = (ButtonDir)i; break; }
if ( RadialMenuDebug.GetBool() ) { Msg( "%f: rosette cursor entered %s at %d,%d\n", gpGlobals->curtime, ButtonDirString( m_armedButtonDir ), x, y ); engine->Con_NPrintf( 20, "%d %d %s", x, y, ButtonDirString( m_armedButtonDir ) ); } } }
if ( nNewDir != NUM_BUTTON_DIRS && !input->ControllerModeActive() ) { SetArmedButtonDir( nNewDir ); } }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::UpdateButtonBounds( void ) { // Save off the extents of our child buttons so we can clip the cursor to that later
int wide, tall; GetSize( wide, tall ); m_minButtonX = wide; m_minButtonY = tall; m_maxButtonX = 0; m_maxButtonY = 0;
for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { if ( !m_buttons[i] ) continue;
int hotspotMinX = 0; int hotspotMinY = 0; int hotspotMaxX = 0; int hotspotMaxY = 0; m_buttons[i]->GetHotspotBounds( &hotspotMinX, &hotspotMinY, &hotspotMaxX, &hotspotMaxY );
int buttonX, buttonY; m_buttons[i]->GetPos( buttonX, buttonY );
m_minButtonX = MIN( m_minButtonX, hotspotMinX + buttonX ); m_minButtonY = MIN( m_minButtonY, hotspotMinY + buttonY ); m_maxButtonX = MAX( m_maxButtonX, hotspotMaxX + buttonX ); m_maxButtonY = MAX( m_maxButtonY, hotspotMaxY + buttonY ); }
// First frame, we won't have any hotspots set up, so we get inverted bounds from our initial setup.
// Reverse these, so our button bounds covers our bounds.
if ( m_minButtonX > m_maxButtonX ) { V_swap( m_minButtonX, m_maxButtonX ); }
if ( m_minButtonY > m_maxButtonY ) { V_swap( m_minButtonY, m_maxButtonY ); } }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::OnThink( void ) { int nSlot = vgui::ipanel()->GetMessageContextId( GetVPanel() ); ACTIVE_SPLITSCREEN_PLAYER_GUARD( nSlot );
if ( m_bQuickPingForceClose ) { SetArmedButtonDir( CENTER ); }
C_Portal_Player *pPlayer = ToPortalPlayer( C_BasePlayer::GetLocalPlayer( nSlot ) ); if ( IsCurrentMenuTypeDisabled( pPlayer, m_menuType ) ) { CloseRadialMenuCommand( m_menuType ); }
if ( !IsMouseInputEnabled() ) return;
// See if our target entity has vanished
if ( (m_menuType != MENU_TAUNT) && m_hTargetEntity == NULL && m_vPosition == vec3_invalid ) { StartFade(); return; }
// See if we need to create our glow index
if ( m_nEntityGlowIndex == -1 ) { // dont glow players
C_Portal_Player *pPlayer = dynamic_cast<C_Portal_Player *>(m_hTargetEntity.Get()); if ( !pPlayer && m_menuType != MENU_TAUNT && ( m_hTargetEntity.Get() && m_hTargetEntity.Get()->GetRenderAlpha() > 0) ) { C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); if ( !localPlayer ) return;
m_nEntityGlowIndex = AddGlowToObject( m_hTargetEntity, localPlayer->GetTeamNumber() ); } } else if ( m_menuType == MENU_TAUNT ) { ClearGlowEntity(); }
#if defined(OSX)
int dx, dy; engine->GetMouseDelta( dx, dy ); m_cursorX += dx; m_cursorY += dy; #else
vgui::surface()->SurfaceGetCursorPos( m_cursorX, m_cursorY ); ScreenToLocal( m_cursorX, m_cursorY ); #endif
if ( engine->IsRecordingDemo() ) { int nMousePos[2] = { m_cursorX, m_cursorY }; engine->RecordDemoCustomData( RadialMenuMouseCallback, &nMousePos, sizeof(int) * 2 ); } else if ( engine->IsPlayingDemo() ) { // use saved coords from the callback
m_cursorX = m_demoCursorX; m_cursorY = m_demoCursorY; } int wide, tall; GetSize( wide, tall );
int centerx = wide / 2; int centery = tall / 2;
UpdateButtonBounds();
if ( m_bFirstCentering ) { #ifndef OSX
LocalToScreen( centerx, centery ); vgui::surface()->SurfaceSetCursorPos( centerx, centery ); #endif
m_cursorX = centerx; m_cursorY = centery; SetArmedButtonDir( CENTER );
m_bFirstCentering = false; } else {
float cursorDistX = ( m_cursorX - centerx ); float cursorDistY = ( m_cursorY - centery ); float buttonRadius = MAX( m_maxButtonX - centerx, m_maxButtonY - centery ) * 0.3f;
if ( cursorDistX != 0.0f && cursorDistY != 0.0f ) { float cursorDist = sqrt( Sqr(cursorDistX) + Sqr(cursorDistY) );
// keeping mouse cursor within button radius
if ( cursorDist > buttonRadius ) { cursorDistX *= ( buttonRadius / cursorDist ); cursorDistY *= ( buttonRadius / cursorDist );
m_cursorX = centerx + cursorDistX; m_cursorY = centery + cursorDistY; }
#ifndef OSX
LocalToScreen( m_cursorX, m_cursorY ); if ( RadialMenuDebug.GetBool() ) { Msg( "%f: rosette warping cursor to %d %d\n", gpGlobals->curtime, m_cursorX, m_cursorY ); } vgui::surface()->SurfaceSetCursorPos( m_cursorX, m_cursorY ); #endif
}
float fJoyForward, fJoySide, fJoyPitch, fJoyYaw = 0.0f;
if ( input->ControllerModeActive() ) { input->Joystick_Querry( fJoyForward, fJoySide, fJoyPitch, fJoyYaw );
// Replace if the other stick was pushed further
// We need to use both sticks because they might have southpaw or legacy set
if ( fabsf( fJoyForward ) > fabsf( fJoyPitch ) ) { fJoyPitch = fJoyForward; } else { // Reflip it if the y was inverted because this shouldn't be inverted
static SplitScreenConVarRef s_joy_inverty( "joy_inverty" ); if ( s_joy_inverty.IsValid() && s_joy_inverty.GetBool( nSlot ) ) { fJoyPitch *= -1.0f; } }
// Replace if the other stick was pushed further
// We need to use both sticks because they might have southpaw or legacy set
if ( fabsf( fJoySide ) > fabsf( fJoyYaw ) ) { fJoyYaw = fJoySide; }
} else { fJoyPitch = cursorDistY / buttonRadius; fJoyYaw = cursorDistX / buttonRadius; }
ButtonDir dir = CENTER; // Right stick can select
if ( fabsf( fJoyPitch ) > 0.3f || fabsf( fJoyYaw ) > 0.3f ) { // Right stick is past the dead zone
float fStickAngle = RAD2DEG( atan2( fJoyYaw, fJoyPitch ) );
if ( fStickAngle > 157.5f ) { dir = NORTH; } else if ( fStickAngle > 112.5f ) { dir = NORTH_EAST; } else if ( fStickAngle > 67.5f ) { dir = EAST; } else if ( fStickAngle > 22.5f ) { dir = SOUTH_EAST; } else if ( fStickAngle > -22.5f ) { dir = SOUTH; } else if ( fStickAngle > -67.5f ) { dir = SOUTH_WEST; } else if ( fStickAngle > -112.5f ) { dir = WEST; } else if ( fStickAngle > -157.5f ) { dir = NORTH_WEST; } else { dir = NORTH; } }
CRadialButton *pButton = m_buttons[dir]; if ( pButton && pButton->IsVisible() && pButton->IsEnabled() && !pButton->IsArmed() && m_armedButtonDir != dir ) { // Only allow going back to center if a tiny amount of time has passed
if ( !input->ControllerModeActive() || m_fSelectionLockInTime == 0.0f || gpGlobals->curtime < m_fSelectionLockInTime + cl_rosette_gamepad_lockin_time.GetFloat() || dir != CENTER ) { m_fSelectionLockInTime = gpGlobals->curtime; pButton->SetMaxScale( dir == CENTER ? 1.0f : 0.75f ); SetArmedButtonDir( dir ); } }
if ( m_armedButtonDir != CENTER && m_armedButtonDir != NUM_BUTTON_DIRS ) { float fInterp = RemapValClamped( ( gpGlobals->curtime - m_fSelectionLockInTime ), 0.0f, cl_rosette_gamepad_expand_time.GetFloat(), 0.0f, 1.0f );
CRadialButton *pSelectedButton = m_buttons[ m_armedButtonDir ]; pSelectedButton->SetMaxScale( 0.75f + 0.25f * fInterp );
if ( input->ControllerModeActive() && fInterp >= 1.0f ) { // take action as soon as the player select something on the menu
CloseRadialMenuCommand( m_menuType ); } } } }
void CRadialMenu::OnTick( void ) { BaseClass::OnTick();
for ( int nPlayer = 0; nPlayer < MAX_SPLITSCREEN_PLAYERS; ++nPlayer ) { ACTIVE_SPLITSCREEN_PLAYER_GUARD( nPlayer ); CUtlVector< SignifierInfo_t > *pSignifierQueue = GetSignifierQueue(); for ( int i = pSignifierQueue->Count() - 1; i >= 0; --i ) { if ( (*pSignifierQueue)[ i ].fDisplayTime <= gpGlobals->curtime ) { AddLocator( (*pSignifierQueue)[ i ].hTarget, (*pSignifierQueue)[ i ].vPos, (*pSignifierQueue)[ i ].vNormal, (*pSignifierQueue)[ i ].nPlayerIndex, (*pSignifierQueue)[ i ].szCaption, 0.0f ); pSignifierQueue->Remove( i ); } } } }
static const char *s_pszRadialMenuIgnoreActions[] = { "forward", "back", "moveleft", "moveright", "jump", "duck", "attack", "attack2" };
int CRadialMenu::KeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) { static bool bViewLocked = false; if ( !IsVisible() ) return 1;
if ( !down ) return 1;
ASSERT_LOCAL_PLAYER_RESOLVABLE(); int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
int numIgnore = ARRAYSIZE( s_pszRadialMenuIgnoreActions ); for ( int i=0; i<numIgnore; ++i ) { int count = 0; ButtonCode_t key; do { key = (ButtonCode_t)engine->Key_CodeForBinding( s_pszRadialMenuIgnoreActions[i], nSlot, count, -1 ); if ( IsJoystickCode( key ) ) { key = GetBaseButtonCode( key ); }
if ( keynum == key ) { return 0; } ++count; } while ( key != BUTTON_CODE_INVALID ); }
return 1; }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::SendCommand( const char *commandStr ) { if ( V_strcmp( commandStr, "done" ) == 0 ) { return; }
// Setup the basic command
char szClientCmd[512];
// Tack on our target entity index
int nEntityIndex = ( m_hTargetEntity ) ? m_hTargetEntity->entindex() : -1;
bool bDelayed = false; bool bIsTaunt = false;
if ( StringHasPrefix( commandStr, "taunt" ) ) { bIsTaunt = true;
V_strcpy( szClientCmd, commandStr );
const char *pchTaunt = commandStr + V_strlen( "taunt" ); if ( pchTaunt[ 0 ] == ' ' && pchTaunt[ 1 ] != '\0' ) { pchTaunt++;
GetClientMenuManagerTaunt().IsTauntTeam( pchTaunt ); GetClientMenuManagerTaunt().SetTauntUsed( pchTaunt ); } } else { if ( V_strcmp( commandStr, "countdown" ) == 0 ) { bDelayed = true;
if ( GetSignifierQueue()->Count() ) { // Don't start a new count till the old one finished!
return; } }
// Build a super string (ick)
V_snprintf( szClientCmd, sizeof( szClientCmd ), "signify %s %d %.2f %.2f %.2f %.2f %.2f %.2f %.2f", commandStr, nEntityIndex, ( bDelayed ? 0.3f : 0.0f ), m_vPosition.x, m_vPosition.y, m_vPosition.z, m_vNormal.x, m_vNormal.y, m_vNormal.z ); } // Send it on its way
engine->ClientCmd( szClientCmd );
if ( !bDelayed && !bIsTaunt ) { // Now send to ourself first
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if ( pPlayer ) { AddLocator( m_hTargetEntity, m_vPosition, m_vNormal, pPlayer->entindex(), commandStr, 0.0f ); } } }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::ChooseArmedButton() { StartFade();
CRadialButton *button = NULL; for ( int i=NUM_BUTTON_DIRS-1; i>=0; --i ) { if ( !m_buttons[i] ) continue;
if ( m_buttons[i]->IsVisible() && m_buttons[i]->IsEnabled() && m_buttons[i]->IsArmed() && !m_buttons[i]->GetPassthru() ) { if ( RadialMenuDebug.GetBool() ) { Msg( "%f: Choosing armed button %s\n", gpGlobals->curtime, ButtonDirString( (ButtonDir)i ) ); } button = m_buttons[i]; break; } }
if ( !button && m_armedButtonDir != NUM_BUTTON_DIRS ) { if ( m_buttons[m_armedButtonDir] && m_buttons[m_armedButtonDir]->IsVisible() && m_buttons[m_armedButtonDir]->IsEnabled() ) { if ( RadialMenuDebug.GetBool() ) { Msg( "%f: Choosing saved armed button %s\n", gpGlobals->curtime, ButtonDirString( m_armedButtonDir ) ); } button = m_buttons[m_armedButtonDir]; } }
if ( button ) { KeyValues *command = button->GetCommand(); if ( command ) { const char *commandStr = command->GetString( "command", NULL ); if ( commandStr ) { button->SetChosen( true ); SendCommand( commandStr );
if ( button->GetGLaDOSResponse() > 0 ) { C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); if ( localPlayer ) { char szCmd[ 32 ]; V_snprintf( szCmd, sizeof(szCmd), "CoopPingTool(%i,%i)", ( localPlayer->GetTeamNumber() == TEAM_RED ? 1 : 2 ), button->GetGLaDOSResponse() ); engine->ClientCmd( szCmd ); } } } }
for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { if ( !m_buttons[i] ) continue;
if ( m_buttons[i] == button ) { if( m_menuType == MENU_TAUNT && i == CENTER ) { PlayDeactivateSound(); } continue; }
m_buttons[i]->SetFakeArmed( false ); m_buttons[i]->SetChosen( false ); } } }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::StartFade( void ) { m_fading = true; m_fadeStart = gpGlobals->curtime; SetMouseInputEnabled( false );
ClearGlowEntity(); }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::SetData( KeyValues *data ) { if ( !data ) return;
if ( RadialMenuDebug.GetBool() ) { m_resource->deleteThis(); m_resource = new KeyValues( "RadialMenu" ); m_resource->LoadFromFile( filesystem, "resource/UI/RadialMenu.res" ); }
if ( m_menuData != data ) { if ( m_menuData ) { m_menuData->deleteThis(); }
m_menuData = data->MakeCopy(); }
bool bDisabledButtons[ NUM_BUTTON_DIRS ]; memset( bDisabledButtons, 0, sizeof( bDisabledButtons ) );
for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { if ( !m_buttons[i] ) continue;
ButtonDir dir = (ButtonDir)i; const char *buttonName = ButtonNameFromDir( dir ); KeyValues *buttonData = data->FindKey( buttonName, false ); if ( !buttonData ) { m_buttons[i]->SetVisible( false ); continue; }
KeyValues *resourceControl = m_resource->FindKey( buttonName, false ); if ( resourceControl ) { m_buttons[i]->UpdateHotspots( resourceControl ); m_buttons[i]->InvalidateLayout(); }
m_buttons[i]->SetVisible( true ); m_buttons[i]->SetChosen( false );
const char *text = buttonData->GetString( "text" ); m_buttons[i]->SetText( text );
const char *command = buttonData->GetString( "command" ); m_buttons[i]->SetCommand( command );
const char *image = buttonData->GetString( "icon" ); if ( image && image[0] != '\0' ) { const char *image2 = buttonData->GetString( "icon2" ); if ( image2 && image2[0] != '\0' ) { // Lets decide which image to use based on team
C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); if ( localPlayer && localPlayer->GetTeamNumber() == TEAM_RED ) { m_buttons[i]->SetImage( image2 ); } else { m_buttons[i]->SetImage( image ); } } else { // Only one image possible
m_buttons[i]->SetImage( image ); } }
if ( !command || !*command ) { bDisabledButtons[ i ] = true; m_buttons[i]->SetEnabled( false ); m_buttons[i]->SetPulse( false ); } else { m_buttons[i]->SetEnabled( true ); m_buttons[i]->SetPulse( false ); //m_buttons[i]->SetPulse( buttonData->GetBool( "new" ) );
}
m_buttons[i]->SetGLaDOSResponse( buttonData->GetInt( "glados", 0 ) );
m_buttons[i]->ShowSubmenuIndicator( Q_strncasecmp( "radialmenu ", command, Q_strlen( "radialmenu " ) ) == 0 );
const char *owner = buttonData->GetString( "owner", NULL ); if ( owner ) { ButtonDir dir = DirFromButtonName( owner ); m_buttons[i]->SetPassthru( m_buttons[dir] ); } else { m_buttons[i]->SetPassthru( NULL ); }
m_buttons[i]->SetArmed( false ); } }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::ClearGlowEntity( void ) { // Stop glowing if we're done
if ( m_nEntityGlowIndex != -1 ) { g_GlowObjectManager.UnregisterGlowObject( m_nEntityGlowIndex ); m_nEntityGlowIndex = -1; } }
//--------------------------------------------------------------------------------------------------------
void CRadialMenu::OnRadialMenuOpen( void ) { if ( m_nArrowTexture == -1 ) { m_nArrowTexture = vgui::surface()->CreateNewTextureID(); vgui::surface()->DrawSetTextureFile( m_nArrowTexture, RADIAL_MENU_POINTER_TEXTURE, true, false ); }
m_fading = false; m_fadeStart = 0.0f;
vgui::Button *firstButton = NULL; for ( int i=0; i<NUM_BUTTON_DIRS; ++i ) { if ( !m_buttons[i] || !m_buttons[i]->IsVisible() || !m_buttons[i]->IsEnabled() ) continue;
if ( firstButton ) { // already found another valid button. since we have at least 2, we can show the menu.
SetMouseInputEnabled( true ); return; }
firstButton = m_buttons[i]; } }
//--------------------------------------------------------------------------------------------------------
void FlushClientMenus( void ) { TheClientMenuManager.Flush();
TheClientMenuManagerPlaytest.Flush();
for ( int i = 0; i < MAX_SPLITSCREEN_PLAYERS; ++i ) { GetClientMenuManagerTaunt( i ).Flush(); } }
//--------------------------------------------------------------------------------------------------------
void OpenRadialMenu( const char *lpszTargetClassification, EHANDLE hTargetEntity, const Vector &vPosition, const Vector &vNormal, RadialMenuTypes_t menuType ) { // FIXME: Need the equivalent here...
// if ( GetTerrorClientMode() && GetTerrorClientMode()->IsInTransition() )
// return;
ASSERT_LOCAL_PLAYER_RESOLVABLE(); int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
C_Portal_Player *localPlayer = ToPortalPlayer( C_BasePlayer::GetLocalPlayer( nSlot ) ); if ( IsCurrentMenuTypeDisabled( localPlayer, menuType ) ) { return; }
CRadialMenu *pRadialMenu = GET_HUDELEMENT( CRadialMenu ); if ( !pRadialMenu ) return;
pRadialMenu->SetQuickPingForceClose( false );
const char *pchTarget = ( ( lpszTargetClassification && lpszTargetClassification[ 0 ] != '\0' ) ? ( lpszTargetClassification ) : ( "Default" ) );
if ( FStrEq( s_radialMenuName[ nSlot ], pchTarget ) ) { bool wasOpen = pRadialMenu->IsVisible() && !pRadialMenu->IsFading(); if ( wasOpen ) { pRadialMenu->SetRadialType( menuType );
return; } }
if ( RadialMenuDebug.GetBool() ) { FlushClientMenus(); // for now, reload every time
}
// Msg("Hit: %s\n", pchTarget );
ClientMenuManager *pMM; if ( menuType == MENU_TAUNT ) { pMM = &GetClientMenuManagerTaunt(); } else if ( menuType == MENU_PING ) { pMM = &TheClientMenuManager; } else { pMM = &TheClientMenuManagerPlaytest; }
KeyValues *menuKey = pMM->FindMenu( pchTarget ); if ( menuKey == NULL ) { menuKey = pMM->FindMenu( "Default" ); if ( menuKey == NULL ) { DevMsg( "No client menu currently matches %s\n", pchTarget );
ShowRadialMenuPanel( false ); pRadialMenu->SetRadialMenuEnabled( false ); return; } }
V_snprintf( s_radialMenuName[ nSlot ], sizeof( s_radialMenuName[ nSlot ] ), pchTarget ); pRadialMenu->SetData( menuKey );
pRadialMenu->SetRadialType( menuType );
if ( menuType == MENU_TAUNT || input->ControllerModeActive() ) { pRadialMenu->SetFadeInTime( gpGlobals->curtime - 1.0f ); } else { pRadialMenu->SetFadeInTime( gpGlobals->curtime + 0.5f ); }
pRadialMenu->ClearLockInTime();
pRadialMenu->m_bFirstCentering = true;
// Send up the specific data we need passed in
pRadialMenu->SetTargetEntity( hTargetEntity ); pRadialMenu->SetTraceData( vPosition, vNormal );
if ( localPlayer ) { localPlayer->EmitSound( "GameUI.UiCoopHudActivate" ); }
ShowRadialMenuPanel( true ); pRadialMenu->SetRadialMenuEnabled( true ); pRadialMenu->OnRadialMenuOpen(); }
//-----------------------------------------------------------------------------
// Purpose: Find and categorize our intended command target and let the client know
//-----------------------------------------------------------------------------
bool LaunchRadialMenu( int nPlayerSlot, RadialMenuTypes_t menuType ) { char szTarget[ MAX_PATH ] = { 0 }; CBaseEntity *pTargetEntity = NULL;
// Get our local target
C_Portal_Player *pPlayer = ToPortalPlayer( C_BasePlayer::GetLocalPlayer( nPlayerSlot ) ); if ( IsCurrentMenuTypeDisabled( pPlayer, menuType ) ) { return false; }
int nTeamSlot = ( pPlayer->GetTeamNumber() == TEAM_BLUE ? 0 : 1 );
float *pfLastPingTime = &( CRadialMenu::m_fLastPingTime[ GET_ACTIVE_SPLITSCREEN_SLOT() ][ nTeamSlot ] ); int *pfNumPings = &( CRadialMenu::m_nNumPings[ GET_ACTIVE_SPLITSCREEN_SLOT() ][ nTeamSlot ] );
if ( menuType == MENU_TAUNT ) { if ( pPlayer->PredictedAirTimeEnd() > 0.85f ) { engine->ClientCmd( "taunt" );
IGameEvent *event = gameeventmanager->CreateEvent( "player_gesture" ); if ( event ) { event->SetInt( "userid", pPlayer->GetUserID() ); event->SetBool( "air", true ); gameeventmanager->FireEventClientSide( event ); } return false; } } else { float fNextPingTime = *pfLastPingTime + PING_DELAY_BASE + PING_DELAY_INCREMENT * *pfNumPings; float fPastNextPingTime = gpGlobals->curtime - fNextPingTime;
if ( gpGlobals->curtime > *pfLastPingTime && fPastNextPingTime < 0.0f ) { // They've been spamming pings, don't let them place another for now
return false; } }
// Clear this out for safety
Vector vPosition = vec3_invalid; Vector vNormal = vec3_invalid;
if ( menuType != MENU_TAUNT ) { // Find what's under the cursor
Vector vForward; pPlayer->EyeVectors( &vForward ); Vector vEyeTrace = pPlayer->EyePosition() + ( vForward * MAX_TRACE_LENGTH );
Ray_t ray; ray.Init( pPlayer->EyePosition(), vEyeTrace );
float flTheNumberOne = 1.0f; C_Portal_Base2D *pHitPortal = UTIL_Portal_FirstAlongRay( ray, flTheNumberOne ); C_Prop_Portal *pPropPoral = dynamic_cast<C_Prop_Portal *>(pHitPortal);
trace_t tr; // Do a trace that respects portals (allows for portal-linked doors)
CTraceFilterNoPlayers filter1; CTraceFilterSkipTwoEntities filter2( GetPlayerHeldEntity( pPlayer ), pPlayer->GetAttachedObject() ); CTraceFilterChain filter( &filter1, &filter2 ); UTIL_Portal_TraceRay( ray, (MASK_OPAQUE_AND_NPCS|CONTENTS_SLIME), &filter, &tr );
pPlayer->CreatePingPointer( tr.endpos );
// Did we hit a portal?
if ( pHitPortal && pHitPortal->IsActivedAndLinked() && pPropPoral ) { V_snprintf( szTarget, sizeof(szTarget), "Portal.%s", pHitPortal->m_bIsPortal2 ? "Orange" : "Blue" ); pTargetEntity = pHitPortal; } else { // See if we passed through a tractor bream
Ray_t ray;
if ( !( ( tr.DidHitWorld() || ( tr.m_pEnt && tr.m_pEnt->IsBrushModel() ) ) && !( tr.contents & CONTENTS_SLIME ) && !IsNoPortalMaterial( tr ) ) ) { // It's not a portal surface, so maybe they wanted to hit the bridge or tbeam
for ( int i = 0; i < ITriggerTractorBeamAutoList::AutoList().Count(); ++i ) { C_Trigger_TractorBeam *pTractorBeam = static_cast< C_Trigger_TractorBeam* >( ITriggerTractorBeamAutoList::AutoList()[ i ] );
ray.Init( tr.startpos, tr.endpos );
trace_t trTemp; enginetrace->ClipRayToEntity( ray, MASK_SHOT, pTractorBeam, &trTemp );
if ( !trTemp.startsolid && ( trTemp.fraction < 1.0f || trTemp.m_pEnt == pTractorBeam ) ) { tr = trTemp; tr.m_pEnt = ClientEntityList().GetBaseEntity( 0 ); tr.surface.flags |= SURF_NOPORTAL;
// Fix up the surface normal and ping position
Vector vPointOnPath; CalcClosestPointOnLineSegment( tr.endpos, pTractorBeam->GetStartPoint(), pTractorBeam->GetEndPoint(), vPointOnPath, NULL );
tr.plane.normal = tr.endpos - vPointOnPath; VectorNormalize( tr.plane.normal );
tr.endpos = vPointOnPath + tr.plane.normal * pTractorBeam->GetBeamRadius(); } }
// See if we passed through a light bridge
for ( int i = 0; i < IProjectedWallEntityAutoList::AutoList().Count(); ++i ) { C_ProjectedWallEntity *pLightBridge = static_cast< C_ProjectedWallEntity* >( IProjectedWallEntityAutoList::AutoList()[ i ] ); Vector vBridgeUp = pLightBridge->Up(); if ( vBridgeUp.z > -0.4f && vBridgeUp.z < 0.4f ) { // Don't hit wall bridges
continue; }
ray.Init( tr.startpos, tr.endpos );
trace_t trTemp; enginetrace->ClipRayToEntity( ray, MASK_SHOT, pLightBridge, &trTemp );
if ( trTemp.fraction < 1.0f || trTemp.m_pEnt == pLightBridge ) { tr = trTemp; tr.m_pEnt = ClientEntityList().GetBaseEntity( 0 ); tr.surface.flags |= SURF_NOPORTAL;
if ( tr.plane.normal.z < -0.9f ) { // Point down at the bridge from above so we can tell players to stand on it even when pointing from below
tr.plane.normal.z = -tr.plane.normal.z; tr.endpos.z += 2.0f; } } } }
// If it's an entity, just return that
if ( tr.m_pEnt && tr.DidHitNonWorldEntity() && !tr.m_pEnt->IsBrushModel() ) { // Fill out the details
const char *lpszSignifier = tr.m_pEnt->GetSignifierName();
Assert( lpszSignifier != NULL ); V_snprintf( szTarget, sizeof(szTarget), "Entity.%s", lpszSignifier );
// Initially, use this as the target entity
pTargetEntity = tr.m_pEnt;
if ( pTargetEntity && V_strstr( szTarget, "button" ) ) { Vector vecBoundsMax, vecBoundsMin; pTargetEntity->GetRenderBounds( vecBoundsMin, vecBoundsMax ); vPosition = pTargetEntity->WorldSpaceCenter(); vPosition.z += (vecBoundsMax.z - 6); vNormal = Vector( 0, 0, 1 ); }
// Access the entity interface methods to get extra data
ISignifierTarget *pSignifier = dynamic_cast<ISignifierTarget *>(tr.m_pEnt); if ( pSignifier != NULL ) { // See if we're overriding our hit position
if ( pSignifier->OverrideSignifierPosition() ) { pTargetEntity = NULL; pSignifier->GetSignifierPosition( tr.endpos, vPosition, vNormal ); } } } else if ( tr.DidHitWorld() || (tr.m_pEnt && tr.m_pEnt->IsBrushModel()) ) { // We're going to use this position explicitly
vPosition = tr.endpos; vNormal = tr.plane.normal;
if ( tr.contents & CONTENTS_SLIME ) { V_strncpy( szTarget, "World.Slime", sizeof(szTarget) ); } else { if ( IsNoPortalMaterial( tr ) ) { V_strncpy( szTarget, "World.NoPortal_", sizeof(szTarget) ); } else { V_strncpy( szTarget, "World.", sizeof(szTarget) ); }
// If it's the world, then classify it
if ( tr.plane.normal[2] > 0.75f ) { V_strncat( szTarget, "Floor", sizeof(szTarget) ); } else if ( tr.plane.normal[2] < -0.75f ) { V_strncat( szTarget, "Ceiling", sizeof(szTarget) ); } else { V_strncat( szTarget, "Wall", sizeof(szTarget) ); } } } else { // Unknown entity type!
Assert( 0 ); return false; } } }
// Launch the real menu
OpenRadialMenu( szTarget, pTargetEntity, vPosition, vNormal, menuType );
return true; }
//--------------------------------------------------------------------------------------------------------
bool IsRadialMenuOpen( void ) { // Determine whether this window is visible or not
CRadialMenu *pRadialMenu = GET_HUDELEMENT( CRadialMenu ); if ( pRadialMenu ) { bool isOpen = pRadialMenu->IsVisible() && !pRadialMenu->IsFading(); return isOpen; }
return false; }
//--------------------------------------------------------------------------------------------------------
bool OpenRadialMenuCommand( RadialMenuTypes_t menuType ) { if ( !g_pGameRules ) return false;
if ( menuType != MENU_PLAYTEST && !g_pGameRules->IsMultiplayer() ) return false;
ASSERT_LOCAL_PLAYER_RESOLVABLE(); int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
if ( s_mouseMenuKeyHeld[ nSlot ] ) return true;
bool bSuccess = LaunchRadialMenu( nSlot, menuType );
if ( bSuccess ) { s_mouseMenuKeyHeld[ nSlot ] = true; }
return bSuccess; }
void openradialmenu( const CCommand &args ) { OpenRadialMenuCommand( MENU_PING ); } static ConCommand mouse_menu_open( "+mouse_menu", openradialmenu, "Opens a menu while held" );
void openradialmenutaunt( const CCommand &args ) { OpenRadialMenuCommand( MENU_TAUNT ); } static ConCommand mouse_menu_taunt_open( "+mouse_menu_taunt", openradialmenutaunt, "Opens a menu while held" );
void openradialmenuplaytest( const CCommand &args ) { OpenRadialMenuCommand( MENU_PLAYTEST ); } static ConCommand mouse_menu_playtest_open( "+mouse_menu_playtest", openradialmenuplaytest, "Opens a menu while held" );
//--------------------------------------------------------------------------------------------------------
void CloseRadialMenuCommand( RadialMenuTypes_t menuType, bool bForceClose /*= false*/ ) { if ( menuType != MENU_PLAYTEST && ( !g_pGameRules || !g_pGameRules->IsMultiplayer() ) ) return;
ASSERT_LOCAL_PLAYER_RESOLVABLE(); int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
CRadialMenu *pRadialMenu = GET_HUDELEMENT( CRadialMenu ); if ( !pRadialMenu ) return;
// Get our local target
C_Portal_Player *pPlayer = ToPortalPlayer( C_BasePlayer::GetLocalPlayer( nSlot ) ); if ( pPlayer ) pPlayer->DestroyPingPointer();
s_mouseMenuKeyHeld[ nSlot ] = false;
if ( !cl_fastradial.GetBool() ) { return; }
bool wasOpen = pRadialMenu->IsVisible() && !pRadialMenu->IsFading();
if ( wasOpen || bForceClose ) { pRadialMenu->ChooseArmedButton();
int wide, tall; pRadialMenu->GetSize( wide, tall ); wide /= 2; tall /= 2; pRadialMenu->LocalToScreen( wide, tall ); vgui::surface()->SurfaceSetCursorPos( wide, tall );
if ( bForceClose ) { ShowRadialMenuPanel( false ); pRadialMenu->SetRadialMenuEnabled( false ); }
if ( !bForceClose ) { input->Joystick_ForceRecentering( 0 ); input->Joystick_ForceRecentering( 1 ); } } else if ( !pRadialMenu->IsVisible() ) { ShowRadialMenuPanel( false ); }
s_radialMenuName[ nSlot ][0] = 0; }
void closeradialmenu( const CCommand &args ) { CloseRadialMenuCommand( MENU_PING ); } static ConCommand mouse_menu_close( "-mouse_menu", closeradialmenu, "Executes the highlighted button on the radial menu (if cl_fastradial is 1)" );
void closeradialmenutaunt( const CCommand &args ) { CloseRadialMenuCommand( MENU_TAUNT ); } static ConCommand mouse_menu_taunt_close( "-mouse_menu_taunt", closeradialmenutaunt, "Executes the highlighted button on the radial menu (if cl_fastradial is 1)" );
void closeradialmenuplaytest( const CCommand &args ) { CloseRadialMenuCommand( MENU_PLAYTEST ); } static ConCommand mouse_menu_playtest_close( "-mouse_menu_playtest", closeradialmenuplaytest, "Executes the highlighted button on the radial menu (if cl_fastradial is 1)" );
extern bool UTIL_EntityBoundsToSizes( C_BaseEntity *pTarget, int *pMinX, int *pMinY, int *pMaxX, int *pMaxY ); extern bool UTIL_WorldSpaceToScreensSpaceBounds( const Vector &vecCenter, const Vector &mins, const Vector &maxs, Vector2D *pMins, Vector2D *pMaxs );
void cc_quickping( const CCommand &args ) { if ( !g_pGameRules || !g_pGameRules->IsMultiplayer() ) return;
if ( !OpenRadialMenuCommand( MENU_PING ) ) { return; }
CRadialMenu *pRadialMenu = GET_HUDELEMENT( CRadialMenu ); if ( pRadialMenu ) { pRadialMenu->SetArmedButtonDir( CRadialMenu::CENTER ); pRadialMenu->SetQuickPingForceClose( true ); } else { CloseRadialMenuCommand( MENU_PING, true ); } } static ConCommand quick_ping("+quick_ping", cc_quickping, "Ping the center option from the ping menu.");
void closequickping( const CCommand &args ) { } static ConCommand quick_ping_close( "-quick_ping", closequickping, "Quick ping is unpressed... nothing to do here." );
//--------------------------------------------------------------------------------------------------------
class CSignifierSystem : public CAutoGameSystemPerFrame { struct SignifierData_t { SignifierData_t( int index, int glowIndex, int playerIndex, int splitscreenID, EHANDLE hEntity, const Vector &vOrigin, float lifetime ) : m_nLocatorIndex( index ), m_nGlowIndex( glowIndex ), m_nPlayerIndex( playerIndex ), m_nSplitscreenID( splitscreenID ), m_hTargetEntity( hEntity), m_vOrigin( vOrigin ), m_flLifetime( lifetime ) { }
int m_nLocatorIndex; int m_nGlowIndex; EHANDLE m_hTargetEntity; Vector m_vOrigin; float m_flLifetime; int m_nPlayerIndex; int m_nSplitscreenID; };
public: CSignifierSystem() : CAutoGameSystemPerFrame( "CSignifierSystem" ) {} int GetObjectScreenHeight( C_BaseEntity *pObject ) { if ( pObject == NULL ) return 0;
int minY = 0; int maxY = 0; UTIL_EntityBoundsToSizes( pObject, NULL, &minY, NULL, &maxY );
return abs( maxY - minY ); }
// Pre-render
virtual void PreRender( void ) { C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); bool bDead = pPlayer && !pPlayer->IsAlive();
// Work through all our indicators and keep them up to date
FOR_EACH_VEC_BACK( m_Signifiers, itr ) { CLocatorTarget *pLocator = Locator_GetTargetFromHandle( m_Signifiers[itr].m_nLocatorIndex ); if ( pLocator ) { const int HEIGHT_PAD = 8; int nPad = ( pLocator->GetIconHeight() / 2 ) + HEIGHT_PAD;
// Update our position
if ( m_Signifiers[itr].m_hTargetEntity != NULL ) { pLocator->m_vecOrigin = m_Signifiers[itr].m_hTargetEntity->WorldSpaceCenter();
if ( V_strstr( m_Signifiers[itr].m_hTargetEntity->GetSignifierName(), "button" ) ) { Vector vecBoundsMax, vecBoundsMin; m_Signifiers[itr].m_hTargetEntity.Get()->GetRenderBounds( vecBoundsMin, vecBoundsMax ); pLocator->m_vecOrigin.z += vecBoundsMax.z + 42; } else { // We'd actually like to find the size of this entity on the screen and make the indicator float above the target!
int nObjectHeight = GetObjectScreenHeight( m_Signifiers[itr].m_hTargetEntity.Get() ); int nHeightOffset = ( nObjectHeight / 2 ); pLocator->m_offsetY = -(nHeightOffset+nPad); } } else { // don't offset anymore
/*
Vector2D mins, maxs; UTIL_WorldSpaceToScreensSpaceBounds( m_Signifiers[itr].m_vOrigin, -Vector(8,8,8), Vector(8,8,8), &mins, &maxs );
int nHeightOffset = abs(maxs.y - mins.y); pLocator->m_offsetY = -(nHeightOffset+nPad); */ }
pLocator->Update(); // Fade away
if ( bDead || m_Signifiers[itr].m_flLifetime < gpGlobals->curtime || ( m_Signifiers[itr].m_hTargetEntity == NULL && m_Signifiers[itr].m_vOrigin == vec3_invalid ) ) { // Remove the glow
if ( m_Signifiers[itr].m_nGlowIndex != -1 ) { g_GlowObjectManager.UnregisterGlowObject( m_Signifiers[itr].m_nGlowIndex ); }
Locator_RemoveTarget( m_Signifiers[itr].m_nLocatorIndex ); m_Signifiers.FastRemove( itr ); } } } }
virtual void LevelShutdownPostEntity() { // Shut down all glows
FOR_EACH_VEC( m_Signifiers, itr ) { if ( m_Signifiers[itr].m_nGlowIndex != -1 ) { g_GlowObjectManager.UnregisterGlowObject( m_Signifiers[itr].m_nGlowIndex ); } }
// Clear the list
m_Signifiers.Purge(); }
// Add an indicator to the world
void AddLocator( const char *lpszIconName, int nPlayerIndex, C_BaseEntity *pTarget, const Vector &vPosition, float flLifetime, Color rgbaColor, bool bScaleByDistance ) { C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); if ( pLocalPlayer && !pLocalPlayer->IsAlive() ) { return; }
int nRecycledGlowIndex = -1;
// First, remove any duplicated icons
FOR_EACH_VEC_BACK( m_Signifiers, itr ) { float flAlpha = 255; if ( pTarget ) flAlpha = pTarget->GetRenderAlpha();
// We only want one glow handle per entity, so "steal" the glow control away from older locators
if ( m_Signifiers[itr].m_hTargetEntity && m_Signifiers[itr].m_hTargetEntity == pTarget && m_Signifiers[itr].m_nSplitscreenID == GET_ACTIVE_SPLITSCREEN_SLOT() && flAlpha > 0 ) { nRecycledGlowIndex = m_Signifiers[itr].m_nGlowIndex; m_Signifiers[itr].m_nGlowIndex = -1;
// Kill it
Locator_RemoveTarget( m_Signifiers[itr].m_nLocatorIndex ); m_Signifiers.FastRemove( itr ); } else { CLocatorTarget *pLocator = Locator_GetTargetFromHandle( m_Signifiers[itr].m_nLocatorIndex ); if ( pLocator ) { if ( m_Signifiers[itr].m_nPlayerIndex == nPlayerIndex && FStrEq( pLocator->GetOnscreenIconTextureName(), lpszIconName ) ) { // Remove the glow
if ( m_Signifiers[itr].m_nGlowIndex != -1 ) { g_GlowObjectManager.UnregisterGlowObject( m_Signifiers[itr].m_nGlowIndex ); }
// Kill it
Locator_RemoveTarget( m_Signifiers[itr].m_nLocatorIndex ); m_Signifiers.FastRemove( itr ); } } } }
// Now add a fresh one
int nIndex = Locator_AddTarget(); CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( nIndex ); if ( pLocatorTarget ) { pLocatorTarget->m_vecOrigin = ( pTarget != NULL ) ? pTarget->WorldSpaceCenter() : vPosition; pLocatorTarget->SetOnscreenIconTextureName( lpszIconName ); pLocatorTarget->SetOffscreenIconTextureName( lpszIconName ); pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_FORCE_CAPTION ); // Draw even when occluded
if ( bScaleByDistance ) { pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_SCALE_BY_DIST ); } else { pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_SCALE_LARGE ); }
pLocatorTarget->SetIconColor( rgbaColor );
pLocatorTarget->Update(); }
// dont glow players
bool bTargetIsPlayer = ToPortalPlayer( pTarget ) != NULL;
// Now, add a glow to this entity (unless we're recycling an old one)
C_BasePlayer *pPingPlayer = UTIL_PlayerByIndex( nPlayerIndex );
bool bRecycled = ( nRecycledGlowIndex != -1 && !bTargetIsPlayer );
int nGlowIndex = bRecycled ? nRecycledGlowIndex : AddGlowToObject( pTarget, pPingPlayer ? pPingPlayer->GetTeamNumber() : 0 );
if ( bRecycled ) { Vector vColor; TeamPingColor( pPingPlayer ? pPingPlayer->GetTeamNumber() : 0, vColor );
g_GlowObjectManager.SetColor( nGlowIndex, vColor ); }
// Hold it
m_Signifiers.AddToTail( SignifierData_t( nIndex, nGlowIndex, nPlayerIndex, GET_ACTIVE_SPLITSCREEN_SLOT(), pTarget, vPosition, flLifetime ) ); } CUtlVector<SignifierData_t> m_Signifiers; }; CSignifierSystem g_SignifierSystem;
//--------------------------------------------------------------------------------------------------------
inline bool IsPortalOnFloor( CBaseEntity *pEntity ) { C_Portal_Base2D *pHitPortal = dynamic_cast<C_Portal_Base2D *>(pEntity); if ( pHitPortal ) { Vector vForward; pHitPortal->GetVectors( &vForward, NULL, NULL ); return ( vForward[2] > 0.75f ); }
return false; }
//--------------------------------------------------------------------------------------------------------
CEG_NOINLINE void PlaceCommandTargetDecal( const Vector &vPosition, const Vector &vNormal, int iTeam, bool bJustArrows ) { // Recreate the trace that got us here
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
if ( !pPlayer ) return;
QAngle angNormal; VectorAngles( vNormal, angNormal ); angNormal.x += 90.0f;
Color color( 255, 255, 255 ); if ( iTeam == TEAM_RED ) color = UTIL_Portal_Color( 2, 0 ); //orange
else color = UTIL_Portal_Color( 1, 0 ); //blue
Vector vColor; vColor.x = color.r(); vColor.y = color.g(); vColor.z = color.b();
if ( bJustArrows ) { DispatchParticleEffect( "command_target_ping_just_arrows", vPosition, vColor, angNormal ); } else { DispatchParticleEffect( "command_target_ping", vPosition, vColor, angNormal ); } }
CEG_PROTECT_FUNCTION( PlaceCommandTargetDecal );
//--------------------------------------------------------------------------------------------------------
void AddLocator( C_BaseEntity *pTarget, const Vector &vPosition, const Vector &vNormal, int nPlayerIndex, const char *caption, float fDisplayTime ) { int nSplitscreenSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
bool bCountdown = StringHasPrefix( caption, "countdown" );
if ( fDisplayTime > gpGlobals->curtime ) { if ( bCountdown ) { IGameEvent *event = gameeventmanager->CreateEvent( "player_countdown" ); if ( event ) { C_BasePlayer *pPlayer = UTIL_PlayerByIndex( nPlayerIndex ); event->SetInt( "userid", pPlayer ? pPlayer->GetUserID() : 0 ); gameeventmanager->FireEventClientSide( event ); }
// Count down animates
CUtlVector< SignifierInfo_t > *pSignifierQueue = GetSignifierQueue();
int nNew = pSignifierQueue->AddToTail(); SignifierInfo_t *pNewSignifier = &((*pSignifierQueue)[ nNew ]); pNewSignifier->hTarget = pTarget; pNewSignifier->vPos = vPosition; pNewSignifier->vNormal = vNormal; pNewSignifier->nPlayerIndex = nPlayerIndex; V_strcpy( pNewSignifier->szCaption, "countdown_3" ); pNewSignifier->fDisplayTime = fDisplayTime;
nNew = pSignifierQueue->AddToTail(); pNewSignifier = &((*pSignifierQueue)[ nNew ]); pNewSignifier->hTarget = pTarget; pNewSignifier->vPos = vPosition; pNewSignifier->vNormal = vNormal; pNewSignifier->nPlayerIndex = nPlayerIndex; V_strcpy( pNewSignifier->szCaption, "countdown_2" ); pNewSignifier->fDisplayTime = fDisplayTime + 1.0f;
nNew = pSignifierQueue->AddToTail(); pNewSignifier = &((*pSignifierQueue)[ nNew ]); pNewSignifier->hTarget = pTarget; pNewSignifier->vPos = vPosition; pNewSignifier->vNormal = vNormal; pNewSignifier->nPlayerIndex = nPlayerIndex; V_strcpy( pNewSignifier->szCaption, "countdown_1" ); pNewSignifier->fDisplayTime = fDisplayTime + 2.0f;
nNew = pSignifierQueue->AddToTail(); pNewSignifier = &((*pSignifierQueue)[ nNew ]); pNewSignifier->hTarget = pTarget; pNewSignifier->vPos = vPosition; pNewSignifier->vNormal = vNormal; pNewSignifier->nPlayerIndex = nPlayerIndex; V_strcpy( pNewSignifier->szCaption, "countdown_go" ); pNewSignifier->fDisplayTime = fDisplayTime + 3.0f; } else { // Put it in the queue for later display
CUtlVector< SignifierInfo_t > *pSignifierQueue = GetSignifierQueue();
int nNew = pSignifierQueue->AddToTail(); SignifierInfo_t *pNewSignifier = &((*pSignifierQueue)[ nNew ]); pNewSignifier->hTarget = pTarget; pNewSignifier->vPos = vPosition; pNewSignifier->vNormal = vNormal; pNewSignifier->nPlayerIndex = nPlayerIndex; V_strcpy( pNewSignifier->szCaption, caption ); pNewSignifier->fDisplayTime = fDisplayTime; } return; }
C_BasePlayer *pPlayer = UTIL_PlayerByIndex( nPlayerIndex ); int nTeamSlot = ( ( pPlayer && pPlayer->GetTeamNumber() == TEAM_BLUE ) ? ( 0 ) : ( 1 ) );
float *pfLastPingTime = &( CRadialMenu::m_fLastPingTime[ nSplitscreenSlot ][ nTeamSlot ] ); int *pfNumPings = &( CRadialMenu::m_nNumPings[ nSplitscreenSlot ][ nTeamSlot ] );
if ( !bCountdown ) { // Reduce the current ping count for ones that have faded by now
float fNextPingTime = *pfLastPingTime + PING_DELAY_BASE + PING_DELAY_INCREMENT * *pfNumPings; float fPastNextPingTime = gpGlobals->curtime - fNextPingTime;
if ( gpGlobals->curtime > *pfLastPingTime && fPastNextPingTime < 0.0f ) { // They've been spamming pings, don't let them place another for now
return; } else if ( *pfNumPings > 0 ) { // Pop off old pings that last about 2.5 seconds
float fOldestPingFadeTime = *pfLastPingTime + 2.5f;
for ( int i = 1; i <= *pfNumPings; ++i ) { fOldestPingFadeTime -= i * PING_DELAY_INCREMENT; }
float fTimePastOldestFaded = gpGlobals->curtime - fOldestPingFadeTime;
while ( *pfNumPings > 0 && fTimePastOldestFaded > 0.0f ) { fTimePastOldestFaded -= *pfNumPings * PING_DELAY_INCREMENT; (*pfNumPings)--; } } }
*pfLastPingTime = gpGlobals->curtime; (*pfNumPings)++;
const char *lpszCommand = caption; char szIconName[MAX_PATH]; bool bAddDecal = false; bool bColor = true; bool bScaleByDistance = true;
char szSound[ 32 ]; V_strncpy( szSound, PING_SOUND_NAME, sizeof( szSound ) );
if ( FStrEq( lpszCommand, "look" ) ) { V_strncpy( szIconName, "icon_look", sizeof(szIconName) ); bAddDecal = true; } else if ( FStrEq( lpszCommand, "death_blue" ) ) { V_strncpy( szIconName, "icon_death_blue", sizeof(szIconName) ); bColor = false; bScaleByDistance = false; szSound[ 0 ] = '\0'; } else if ( FStrEq( lpszCommand, "death_orange" ) ) { V_strncpy( szIconName, "icon_death_orange", sizeof(szIconName) ); bColor = false; bScaleByDistance = false; szSound[ 0 ] = '\0'; } else if ( FStrEq( lpszCommand, "countdown_3" ) ) { V_strncpy( szIconName, "icon_countdown_3", sizeof(szIconName) ); bScaleByDistance = false; V_strncpy( szSound, PING_SOUND_NAME_LOW, sizeof( szSound ) ); } else if ( FStrEq( lpszCommand, "countdown_2" ) ) { V_strncpy( szIconName, "icon_countdown_2", sizeof(szIconName) ); bScaleByDistance = false; V_strncpy( szSound, PING_SOUND_NAME_LOW, sizeof( szSound ) ); } else if ( FStrEq( lpszCommand, "countdown_1" ) ) { V_strncpy( szIconName, "icon_countdown_1", sizeof(szIconName) ); bScaleByDistance = false; V_strncpy( szSound, PING_SOUND_NAME_LOW, sizeof( szSound ) ); } else if ( FStrEq( lpszCommand, "countdown_go" ) ) { V_strncpy( szIconName, "icon_countdown_go", sizeof(szIconName) ); bScaleByDistance = false; V_strncpy( szSound, PING_SOUND_NAME_HIGH, sizeof( szSound ) ); } else if ( FStrEq( lpszCommand, "move_here" ) ) { V_strncpy( szIconName, "icon_move_here", sizeof(szIconName) ); bAddDecal = true; } else if ( FStrEq( lpszCommand, "portal_place_floor" ) ) { V_strncpy( szIconName, "icon_portal_place_floor", sizeof(szIconName) ); bAddDecal = true; } else if ( FStrEq( lpszCommand, "portal_place_wall" ) ) { V_strncpy( szIconName, "icon_portal_place_wall", sizeof(szIconName) ); bAddDecal = true; } else if ( FStrEq( lpszCommand, "portal_move" ) ) { V_strncpy( szIconName, "icon_portal_move", sizeof(szIconName) ); } else if ( FStrEq( lpszCommand, "portal_enter" ) ) { if ( IsPortalOnFloor( pTarget ) ) { V_strncpy( szIconName, "icon_portal_enter_floor", sizeof(szIconName) ); } else { V_strncpy( szIconName, "icon_portal_enter_wall", sizeof(szIconName) ); } } else if ( FStrEq( lpszCommand, "portal_exit" ) ) { if ( IsPortalOnFloor( pTarget ) ) { V_strncpy( szIconName, "icon_portal_exit_floor", sizeof(szIconName) ); } else { V_strncpy( szIconName, "icon_portal_exit_wall", sizeof(szIconName) ); } } else if ( FStrEq( lpszCommand, "slime_no_drink" ) ) { V_strncpy( szIconName, "icon_slime_no_drink", sizeof(szIconName) ); } else if ( FStrEq( lpszCommand, "box_pickup" ) ) { V_strncpy( szIconName, "icon_box_pickup", sizeof(szIconName) ); } else if ( FStrEq( lpszCommand, "box_putdown" ) ) { V_strncpy( szIconName, "icon_box_drop", sizeof(szIconName) ); bAddDecal = true; } else if ( FStrEq( lpszCommand, "button_press" ) ) { V_strncpy( szIconName, "icon_button_press", sizeof(szIconName) ); } else if ( FStrEq( lpszCommand, "button_tall_press" ) ) { V_strncpy( szIconName, "icon_button_tall_press", sizeof(szIconName) ); } else if ( FStrEq( lpszCommand, "turret_warning" ) ) { V_strncpy( szIconName, "icon_turret_warning", sizeof(szIconName) ); } else if ( FStrEq( lpszCommand, "love_it" ) ) { V_strncpy( szIconName, "icon_love_it", sizeof( szIconName ) ); bAddDecal = true; } else if ( FStrEq( lpszCommand, "stuck" ) ) { V_strncpy( szIconName, "icon_stuck", sizeof( szIconName ) ); bAddDecal = true; } else if ( FStrEq( lpszCommand, "hate_it" ) ) { V_strncpy( szIconName, "icon_hate_it", sizeof( szIconName ) ); bAddDecal = true; } else if ( FStrEq( lpszCommand, "confused" ) ) { V_strncpy( szIconName, "icon_confused", sizeof( szIconName ) ); bAddDecal = true; } else if ( FStrEq( lpszCommand, "done" ) ) { return; } else { // Found an unknown command!
Assert(0); return; }
C_Team *pTeam = pPlayer ? pPlayer->GetTeam() : NULL;
// Add a decal down where we pointed
if ( bAddDecal ) { bool bJustArrows = false; if ( pTarget && V_strstr( pTarget->GetSignifierName(), "button" ) ) { bJustArrows = true; }
int iTeam = pTeam ? pTeam->GetTeamNumber() : 0; PlaceCommandTargetDecal( vPosition, vNormal, iTeam, bJustArrows ); }
Color color( 255, 255, 255, 255 );
if ( pPlayer ) { if ( szSound[ 0 ] != '\0' ) { pPlayer->EmitSound( szSound ); }
if ( bColor ) { if ( pTeam && pTeam->GetTeamNumber() == TEAM_RED ) { color = UTIL_Portal_Color( 2, 0 ); //orange
} else { color = UTIL_Portal_Color( 1, 0 ); //blue
} } }
// if single player playtest, our alpha will come back 0, push it up
if ( color.a() == 0 ) { color.SetColor( color.r(), color.g(), color.b(), 255 ); }
float flLifetime = gpGlobals->curtime + ( bCountdown ? 1.0f : 3.0f ); Vector vecSigPos = vPosition; vecSigPos += vNormal*64; g_SignifierSystem.AddLocator( szIconName, nPlayerIndex, pTarget, vecSigPos, flLifetime, color, bScaleByDistance ); }
//--------------------------------------------------------------------------------------------------------
static void __MsgFunc_AddLocator( bf_read &msg ) { // Find the index of the sending player
int nPlayerIndex = msg.ReadShort();
// Find the entity in question
C_BaseEntity *pTarget = UTIL_EntityFromUserMessageEHandle( msg.ReadLong() );
float fDisplayTime = msg.ReadFloat(); Vector vPosition = vec3_invalid; Vector vNormal = vec3_invalid; msg.ReadBitVec3Coord( vPosition ); msg.ReadBitVec3Normal( vNormal );
// Find the name of the icon to show
char iconName[2048]; msg.ReadString( iconName, sizeof(iconName) ); AddLocator( pTarget, vPosition, vNormal, nPlayerIndex, iconName, fDisplayTime ); }
USER_MESSAGE_REGISTER( AddLocator );
|