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

//===== 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 );