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.
422 lines
9.5 KiB
422 lines
9.5 KiB
![]()
4 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
#include "cbase.h"
|
||
|
#include <string.h>
|
||
|
#include <stdio.h>
|
||
|
#include "voice_status.h"
|
||
|
#include "radio_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 "c_cs_player.h"
|
||
|
#include "menu.h" // for CHudMenu defs
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
using namespace vgui;
|
||
|
|
||
|
// ---------------------------------------------------------------------- //
|
||
|
// The radio feedback manager for the client.
|
||
|
// ---------------------------------------------------------------------- //
|
||
|
static CRadioStatus s_RadioStatus;
|
||
|
|
||
|
//
|
||
|
//-----------------------------------------------------
|
||
|
//
|
||
|
|
||
|
// Stuff for the Radio Menus
|
||
|
static void radio1_f( void );
|
||
|
static void radio2_f( void );
|
||
|
static void radio3_f( void );
|
||
|
|
||
|
static ConCommand radio1( "radio1", radio1_f, "Opens a radio menu" );
|
||
|
static ConCommand radio2( "radio2", radio2_f, "Opens a radio menu" );
|
||
|
static ConCommand radio3( "radio3", radio3_f, "Opens a radio menu" );
|
||
|
static int g_whichMenu = 0;
|
||
|
|
||
|
//
|
||
|
//--------------------------------------------------------------
|
||
|
//
|
||
|
// These methods will bring up the radio menus from the client side.
|
||
|
// They mimic the old server commands of the same name, which used
|
||
|
// to require a round-trip causing latency and unreliability in
|
||
|
// menu responses. Only 1 message is sent to the server now which
|
||
|
// includes both the menu name and the selected item. The server
|
||
|
// is never informed that the menu has been displayed.
|
||
|
//
|
||
|
//--------------------------------------------------------------
|
||
|
//
|
||
|
void OpenRadioMenu( int index )
|
||
|
{
|
||
|
// do not show the menu if the player is dead or is an observer
|
||
|
C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer();
|
||
|
if ( !pPlayer )
|
||
|
return;
|
||
|
|
||
|
if ( !pPlayer->IsAlive() || pPlayer->IsObserver() )
|
||
|
return;
|
||
|
|
||
|
CHudMenu *pMenu = (CHudMenu *) gHUD.FindElement( "CHudMenu" );
|
||
|
if ( !pMenu )
|
||
|
return;
|
||
|
|
||
|
g_whichMenu = index;
|
||
|
|
||
|
//
|
||
|
// the 0x23f and 0x3ff are masks that describes the keys that
|
||
|
// are valid. This will have to be changed if the menus change.
|
||
|
//
|
||
|
switch ( index )
|
||
|
{
|
||
|
case 1:
|
||
|
pMenu->ShowMenu( "#RadioA", 0x23f );
|
||
|
break;
|
||
|
case 2:
|
||
|
pMenu->ShowMenu( "#RadioB", 0x23f );
|
||
|
break;
|
||
|
case 3:
|
||
|
pMenu->ShowMenu( "#RadioC", 0x3ff );
|
||
|
break;
|
||
|
default:
|
||
|
g_whichMenu = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void radio1_f( void )
|
||
|
{
|
||
|
OpenRadioMenu( 1 );
|
||
|
}
|
||
|
|
||
|
static void radio2_f( void )
|
||
|
{
|
||
|
OpenRadioMenu( 2 );
|
||
|
}
|
||
|
|
||
|
static void radio3_f( void )
|
||
|
{
|
||
|
OpenRadioMenu( 3 );
|
||
|
}
|
||
|
|
||
|
CON_COMMAND_F( menuselect, "menuselect", FCVAR_CLIENTCMD_CAN_EXECUTE )
|
||
|
{
|
||
|
if ( args.ArgC() < 2 )
|
||
|
return;
|
||
|
|
||
|
if( g_whichMenu == 0 )
|
||
|
{
|
||
|
// if we didn't have a menu open, maybe a plugin did. send it on to the server.
|
||
|
const char *cmd = VarArgs( "menuselect %s", args[1] );
|
||
|
engine->ServerCmd( cmd );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int whichEntry = atoi( args[ 1 ] );
|
||
|
|
||
|
switch( g_whichMenu )
|
||
|
{
|
||
|
case 1: //RadioA
|
||
|
{
|
||
|
switch( whichEntry )
|
||
|
{
|
||
|
case 1: // coverme
|
||
|
engine->ClientCmd( "coverme" );
|
||
|
break;
|
||
|
case 2: // takepoint
|
||
|
engine->ClientCmd( "takepoint" );
|
||
|
break;
|
||
|
case 3: // holdpos
|
||
|
engine->ClientCmd( "holdpos" );
|
||
|
break;
|
||
|
case 4: // regroup
|
||
|
engine->ClientCmd( "regroup" );
|
||
|
break;
|
||
|
case 5: // followme
|
||
|
engine->ClientCmd( "followme" );
|
||
|
break;
|
||
|
case 6: // takingfire
|
||
|
engine->ClientCmd( "takingfire" );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 2: //RadioB
|
||
|
{
|
||
|
switch( whichEntry )
|
||
|
{
|
||
|
case 1: // go
|
||
|
engine->ClientCmd( "go" );
|
||
|
break;
|
||
|
case 2: // fallback
|
||
|
engine->ClientCmd( "fallback" );
|
||
|
break;
|
||
|
case 3: // sticktog
|
||
|
engine->ClientCmd( "sticktog" );
|
||
|
break;
|
||
|
case 4: // getinpos
|
||
|
engine->ClientCmd( "getinpos" );
|
||
|
break;
|
||
|
case 5: // stormfront
|
||
|
engine->ClientCmd( "stormfront" );
|
||
|
break;
|
||
|
case 6: // report
|
||
|
engine->ClientCmd( "report" );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 3: //RadioC
|
||
|
{
|
||
|
switch( whichEntry )
|
||
|
{
|
||
|
case 1: // roger
|
||
|
engine->ClientCmd( "roger" );
|
||
|
break;
|
||
|
case 2: // enemyspot
|
||
|
engine->ClientCmd( "enemyspot" );
|
||
|
break;
|
||
|
case 3: // needbackup
|
||
|
engine->ClientCmd( "needbackup" );
|
||
|
break;
|
||
|
case 4: // sectorclear
|
||
|
engine->ClientCmd( "sectorclear" );
|
||
|
break;
|
||
|
case 5: // inposition
|
||
|
engine->ClientCmd( "inposition" );
|
||
|
break;
|
||
|
case 6: // reportingin
|
||
|
engine->ClientCmd( "reportingin" );
|
||
|
break;
|
||
|
case 7: // getout
|
||
|
engine->ClientCmd( "getout" );
|
||
|
break;
|
||
|
case 8: // negative
|
||
|
engine->ClientCmd( "negative" );
|
||
|
break;
|
||
|
case 9: // enemydown
|
||
|
engine->ClientCmd( "enemydown" );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// if we didn't have a menu open, maybe a plugin did. send it on to the server.
|
||
|
const char *cmd = VarArgs( "menuselect %d", whichEntry );
|
||
|
engine->ServerCmd( cmd );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// reset menu
|
||
|
g_whichMenu = 0;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
//-----------------------------------------------------
|
||
|
//
|
||
|
|
||
|
CRadioStatus* RadioManager()
|
||
|
{
|
||
|
return &s_RadioStatus;
|
||
|
}
|
||
|
|
||
|
|
||
|
// ---------------------------------------------------------------------- //
|
||
|
// CRadioStatus.
|
||
|
// ---------------------------------------------------------------------- //
|
||
|
|
||
|
CRadioStatus::CRadioStatus()
|
||
|
{
|
||
|
m_pHeadLabelMaterial = NULL;
|
||
|
Q_memset(m_radioUntil, 0, sizeof(m_radioUntil));
|
||
|
Q_memset(m_voiceUntil, 0, sizeof(m_voiceUntil));
|
||
|
}
|
||
|
|
||
|
bool CRadioStatus::Init()
|
||
|
{
|
||
|
if ( !m_pHeadLabelMaterial )
|
||
|
{
|
||
|
m_pHeadLabelMaterial = materials->FindMaterial( "sprites/radio", TEXTURE_GROUP_VGUI );
|
||
|
}
|
||
|
|
||
|
if ( IsErrorMaterial( m_pHeadLabelMaterial ) )
|
||
|
return false;
|
||
|
|
||
|
m_pHeadLabelMaterial->IncrementReferenceCount();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void CRadioStatus::Shutdown()
|
||
|
{
|
||
|
if ( m_pHeadLabelMaterial )
|
||
|
m_pHeadLabelMaterial->DecrementReferenceCount();
|
||
|
|
||
|
m_pHeadLabelMaterial = NULL;
|
||
|
}
|
||
|
|
||
|
void CRadioStatus::LevelInitPostEntity()
|
||
|
{
|
||
|
ExpireBotVoice( true );
|
||
|
Q_memset(m_radioUntil, 0, sizeof(m_radioUntil));
|
||
|
Q_memset(m_voiceUntil, 0, sizeof(m_voiceUntil));
|
||
|
}
|
||
|
|
||
|
void CRadioStatus::LevelShutdownPreEntity()
|
||
|
{
|
||
|
ExpireBotVoice( true );
|
||
|
Q_memset(m_radioUntil, 0, sizeof(m_radioUntil));
|
||
|
Q_memset(m_voiceUntil, 0, sizeof(m_voiceUntil));
|
||
|
}
|
||
|
|
||
|
extern float g_flHeadIconSize;
|
||
|
|
||
|
static float s_flHeadOffset = 3;
|
||
|
static float s_flHeadIconSize = 7;
|
||
|
|
||
|
void CRadioStatus::DrawHeadLabels()
|
||
|
{
|
||
|
ExpireBotVoice();
|
||
|
|
||
|
if( !m_pHeadLabelMaterial )
|
||
|
return;
|
||
|
|
||
|
for(int i=0; i < VOICE_MAX_PLAYERS; i++)
|
||
|
{
|
||
|
if ( m_radioUntil[i] < gpGlobals->curtime )
|
||
|
continue;
|
||
|
|
||
|
IClientNetworkable *pClient = cl_entitylist->GetClientEntity( i+1 );
|
||
|
|
||
|
// Don't show an icon if the player is not in our PVS.
|
||
|
if ( !pClient || pClient->IsDormant() )
|
||
|
continue;
|
||
|
|
||
|
C_BasePlayer *pPlayer = dynamic_cast<C_BasePlayer*>(pClient);
|
||
|
if( !pPlayer )
|
||
|
continue;
|
||
|
|
||
|
// Don't show an icon for dead or spectating players (ie: invisible entities).
|
||
|
if( pPlayer->IsPlayerDead() )
|
||
|
continue;
|
||
|
|
||
|
// Place it above his head.
|
||
|
Vector vOrigin = pPlayer->WorldSpaceCenter();
|
||
|
vOrigin.z += GetClientVoiceMgr()->GetHeadLabelOffset() + s_flHeadOffset;
|
||
|
|
||
|
if ( GetClientVoiceMgr()->IsPlayerSpeaking( i+1 ) )
|
||
|
{
|
||
|
vOrigin.z += g_flHeadIconSize;
|
||
|
}
|
||
|
|
||
|
// Align it so it never points up or down.
|
||
|
Vector vUp( 0, 0, 1 );
|
||
|
Vector vRight = CurrentViewRight();
|
||
|
if ( fabs( vRight.z ) > 0.95 ) // don't draw it edge-on
|
||
|
continue;
|
||
|
|
||
|
vRight.z = 0;
|
||
|
VectorNormalize( vRight );
|
||
|
|
||
|
|
||
|
float flSize = s_flHeadIconSize;
|
||
|
|
||
|
CMatRenderContextPtr pRenderContext( materials );
|
||
|
|
||
|
pRenderContext->Bind( m_pHeadLabelMaterial );
|
||
|
IMesh *pMesh = pRenderContext->GetDynamicMesh();
|
||
|
CMeshBuilder meshBuilder;
|
||
|
meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );
|
||
|
|
||
|
meshBuilder.Color3f( 1.0, 1.0, 1.0 );
|
||
|
meshBuilder.TexCoord2f( 0,0,0 );
|
||
|
meshBuilder.Position3fv( (vOrigin + (vRight * -flSize) + (vUp * flSize)).Base() );
|
||
|
meshBuilder.AdvanceVertex();
|
||
|
|
||
|
meshBuilder.Color3f( 1.0, 1.0, 1.0 );
|
||
|
meshBuilder.TexCoord2f( 0,1,0 );
|
||
|
meshBuilder.Position3fv( (vOrigin + (vRight * flSize) + (vUp * flSize)).Base() );
|
||
|
meshBuilder.AdvanceVertex();
|
||
|
|
||
|
meshBuilder.Color3f( 1.0, 1.0, 1.0 );
|
||
|
meshBuilder.TexCoord2f( 0,1,1 );
|
||
|
meshBuilder.Position3fv( (vOrigin + (vRight * flSize) + (vUp * -flSize)).Base() );
|
||
|
meshBuilder.AdvanceVertex();
|
||
|
|
||
|
meshBuilder.Color3f( 1.0, 1.0, 1.0 );
|
||
|
meshBuilder.TexCoord2f( 0,0,1 );
|
||
|
meshBuilder.Position3fv( (vOrigin + (vRight * -flSize) + (vUp * -flSize)).Base() );
|
||
|
meshBuilder.AdvanceVertex();
|
||
|
meshBuilder.End();
|
||
|
pMesh->Draw();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CRadioStatus::UpdateRadioStatus(int entindex, float duration)
|
||
|
{
|
||
|
if(entindex > 0 && entindex <= VOICE_MAX_PLAYERS)
|
||
|
{
|
||
|
int iClient = entindex - 1;
|
||
|
if(iClient < 0)
|
||
|
return;
|
||
|
|
||
|
m_radioUntil[iClient] = gpGlobals->curtime + duration;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CRadioStatus::UpdateVoiceStatus(int entindex, float duration)
|
||
|
{
|
||
|
if(entindex > 0 && entindex <= VOICE_MAX_PLAYERS)
|
||
|
{
|
||
|
int iClient = entindex - 1;
|
||
|
if(iClient < 0)
|
||
|
return;
|
||
|
|
||
|
m_voiceUntil[iClient] = gpGlobals->curtime + duration;
|
||
|
GetClientVoiceMgr()->UpdateSpeakerStatus( entindex, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CRadioStatus::ExpireBotVoice( bool force )
|
||
|
{
|
||
|
for(int i=0; i < VOICE_MAX_PLAYERS; i++)
|
||
|
{
|
||
|
if ( m_voiceUntil[i] > 0.0f )
|
||
|
{
|
||
|
bool expire = force;
|
||
|
|
||
|
C_CSPlayer *player = static_cast<C_CSPlayer*>( cl_entitylist->GetEnt(i+1) );
|
||
|
if ( !player )
|
||
|
{
|
||
|
// player left the game
|
||
|
expire = true;
|
||
|
}
|
||
|
else if ( m_voiceUntil[i] < gpGlobals->curtime )
|
||
|
{
|
||
|
// player is done speaking
|
||
|
expire = true;
|
||
|
}
|
||
|
|
||
|
if ( expire )
|
||
|
{
|
||
|
m_voiceUntil[i] = 0.0f;
|
||
|
GetClientVoiceMgr()->UpdateSpeakerStatus( i+1, false );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|