//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "basetypes.h" #include "hud.h" #include #include #include "voice_status.h" #include "r_efx.h" #include #include #include "cdll_client_int.h" #include "hud_macros.h" #include "c_playerresource.h" #include "cliententitylist.h" #include "c_baseplayer.h" #include "materialsystem/imesh.h" #include "view.h" #include "convar.h" #include #include #include #include "vgui_bitmapimage.h" #include "materialsystem/imaterial.h" #include "tier0/dbg.h" #include "cdll_int.h" #include // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" using namespace vgui; extern int cam_thirdperson; #define VOICE_MODEL_INTERVAL 0.3 #define SQUELCHOSCILLATE_PER_SECOND 2.0f ConVar voice_modenable( "voice_modenable", "1", FCVAR_ARCHIVE | FCVAR_CLIENTCMD_CAN_EXECUTE, "Enable/disable voice in this mod." ); ConVar voice_clientdebug( "voice_clientdebug", "0" ); // ---------------------------------------------------------------------- // // The voice manager for the client. // ---------------------------------------------------------------------- // static CVoiceStatus *g_VoiceStatus = NULL; CVoiceStatus* GetClientVoiceMgr() { if ( !g_VoiceStatus ) { ClientVoiceMgr_Init(); } return g_VoiceStatus; } void ClientVoiceMgr_Init() { if ( g_VoiceStatus ) return; g_VoiceStatus = new CVoiceStatus(); } void ClientVoiceMgr_Shutdown() { delete g_VoiceStatus; g_VoiceStatus = NULL; } // ---------------------------------------------------------------------- // // CVoiceStatus. // ---------------------------------------------------------------------- // static CVoiceStatus *g_pInternalVoiceStatus = NULL; void __MsgFunc_VoiceMask(bf_read &msg) { if(g_pInternalVoiceStatus) g_pInternalVoiceStatus->HandleVoiceMaskMsg(msg); } void __MsgFunc_RequestState(bf_read &msg) { if(g_pInternalVoiceStatus) g_pInternalVoiceStatus->HandleReqStateMsg(msg); } // ---------------------------------------------------------------------- // // CVoiceStatus. // ---------------------------------------------------------------------- // CVoiceStatus::CVoiceStatus() { m_nControlSize = 0; m_bBanMgrInitialized = false; m_LastUpdateServerState = 0; m_bTalking = m_bServerAcked = false; #ifdef VOICE_VOX_ENABLE m_bAboveThresholdTimer.Invalidate(); #endif // VOICE_VOX_ENABLE m_bServerModEnable = -1; m_pHeadLabelMaterial = NULL; m_bHeadLabelsDisabled = false; } CVoiceStatus::~CVoiceStatus() { if ( m_pHeadLabelMaterial ) { m_pHeadLabelMaterial->DecrementReferenceCount(); } g_pInternalVoiceStatus = NULL; const char *pGameDir = engine->GetGameDirectory(); if( pGameDir ) { if(m_bBanMgrInitialized) { m_BanMgr.SaveState( pGameDir ); } } } int CVoiceStatus::Init( IVoiceStatusHelper *pHelper, VPANEL pParentPanel) { const char *pGameDir = engine->GetGameDirectory(); if( pGameDir ) { m_BanMgr.Init( pGameDir ); m_bBanMgrInitialized = true; } Assert(!g_pInternalVoiceStatus); g_pInternalVoiceStatus = this; m_pHeadLabelMaterial = materials->FindMaterial( "voice/icntlk_pl", TEXTURE_GROUP_VGUI ); m_pHeadLabelMaterial->IncrementReferenceCount(); m_bInSquelchMode = false; m_pHelper = pHelper; m_pParentPanel = pParentPanel; HOOK_MESSAGE(VoiceMask); HOOK_MESSAGE(RequestState); return 1; } BitmapImage* vgui_LoadMaterial( vgui::VPANEL pParent, const char *pFilename ) { return new BitmapImage( pParent, pFilename ); } void CVoiceStatus::VidInit() { } void CVoiceStatus::Frame(double frametime) { // check server banned players once per second if (gpGlobals->curtime - m_LastUpdateServerState > 1) { UpdateServerState(false); } } float g_flHeadOffset = 35; float g_flHeadIconSize = 8; void CVoiceStatus::SetHeadLabelOffset( float offset ) { g_flHeadOffset = offset; } float CVoiceStatus::GetHeadLabelOffset( void ) const { return g_flHeadOffset; } void CVoiceStatus::DrawHeadLabels() { if ( m_bHeadLabelsDisabled ) return; if ( GameRules() && ( GameRules()->ShouldDrawHeadLabels() == false ) ) return; if( !m_pHeadLabelMaterial ) return; CMatRenderContextPtr pRenderContext( materials ); for(int i=0; i < VOICE_MAX_PLAYERS; i++) { if ( !m_VoicePlayers[i] ) 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(pClient); if( !pPlayer ) continue; // Don't show an icon for dead or spectating players (ie: invisible entities). if( pPlayer->IsPlayerDead() ) continue; // Place it 20 units above his head. Vector vOrigin = pPlayer->WorldSpaceCenter(); vOrigin.z += g_flHeadOffset; // 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 = g_flHeadIconSize; pRenderContext->Bind( pPlayer->GetHeadLabelMaterial() ); 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 CVoiceStatus::UpdateSpeakerStatus(int entindex, bool bTalking) { if(!m_pParentPanel) return; if( voice_clientdebug.GetInt() ) { Msg( "CVoiceStatus::UpdateSpeakerStatus: ent %d talking = %d\n", entindex, bTalking ); } // Is it the local player talking? if( entindex == -1 ) { m_bTalking = !!bTalking; if( bTalking ) { // Enable voice for them automatically if they try to talk. engine->ClientCmd( "voice_modenable 1" ); } } else if( entindex == -2 ) { m_bServerAcked = !!bTalking; } #ifdef VOICE_VOX_ENABLE else if( entindex == -3 ) { if ( bTalking ) { const float AboveThresholdMinDuration = 0.5f; m_bAboveThresholdTimer.Start( AboveThresholdMinDuration ); } } #endif // VOICE_VOX_ENABLE else if(entindex > 0 && entindex <= VOICE_MAX_PLAYERS) { int iClient = entindex - 1; if(iClient < 0) return; if(bTalking) { m_VoicePlayers[iClient] = true; m_VoiceEnabledPlayers[iClient] = true; } else { m_VoicePlayers[iClient] = false; } } } void CVoiceStatus::UpdateServerState(bool bForce) { // Can't do anything when we're not in a level. if( !g_bLevelInitialized ) { if( voice_clientdebug.GetInt() ) { Msg( "CVoiceStatus::UpdateServerState: g_bLevelInitialized\n" ); } return; } int bCVarModEnable = !!voice_modenable.GetInt(); if(bForce || m_bServerModEnable != bCVarModEnable) { m_bServerModEnable = bCVarModEnable; char str[256]; Q_snprintf(str, sizeof(str), "VModEnable %d", m_bServerModEnable); engine->ServerCmd(str); if( voice_clientdebug.GetInt() ) { Msg( "CVoiceStatus::UpdateServerState: Sending '%s'\n", str ); } } char str[2048]; Q_strncpy(str,"vban",sizeof(str)); bool bChange = false; for(unsigned long dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) { unsigned long serverBanMask = 0; unsigned long banMask = 0; for(unsigned long i=0; i < 32; i++) { int playerIndex = ( dw * 32 + i ); if ( playerIndex >= MAX_PLAYERS ) break; player_info_t pi; if ( !engine->GetPlayerInfo( i+1, &pi ) ) continue; if ( m_BanMgr.GetPlayerBan( pi.guid ) ) { banMask |= 1 << i; } if ( m_ServerBannedPlayers[playerIndex] ) { serverBanMask |= 1 << i; } } if ( serverBanMask != banMask ) { bChange = true; } // Ok, the server needs to be updated. char numStr[512]; Q_snprintf(numStr, sizeof(numStr), " %lx", banMask); Q_strncat(str, numStr, sizeof(str), COPY_ALL_CHARACTERS); } if(bChange || bForce) { if( voice_clientdebug.GetInt() ) { Msg( "CVoiceStatus::UpdateServerState: Sending '%s'\n", str ); } engine->ServerCmd( str, false ); // Tell the server.. } else { if( voice_clientdebug.GetInt() ) { Msg( "CVoiceStatus::UpdateServerState: no change\n" ); } } m_LastUpdateServerState = gpGlobals->curtime; } void CVoiceStatus::HandleVoiceMaskMsg(bf_read &msg) { unsigned int dw; for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) { m_AudiblePlayers.SetDWord(dw, (unsigned long)msg.ReadLong()); m_ServerBannedPlayers.SetDWord(dw, (unsigned long)msg.ReadLong()); if( voice_clientdebug.GetInt()) { Msg("CVoiceStatus::HandleVoiceMaskMsg\n"); Msg(" - m_AudiblePlayers[%d] = %u\n", dw, m_AudiblePlayers.GetDWord(dw)); Msg(" - m_ServerBannedPlayers[%d] = %u\n", dw, m_ServerBannedPlayers.GetDWord(dw)); } } m_bServerModEnable = msg.ReadByte(); } void CVoiceStatus::HandleReqStateMsg(bf_read &msg) { if(voice_clientdebug.GetInt()) { Msg("CVoiceStatus::HandleReqStateMsg\n"); } UpdateServerState(true); } void CVoiceStatus::StartSquelchMode() { if(m_bInSquelchMode) return; m_bInSquelchMode = true; m_pHelper->UpdateCursorState(); } void CVoiceStatus::StopSquelchMode() { m_bInSquelchMode = false; m_pHelper->UpdateCursorState(); } bool CVoiceStatus::IsInSquelchMode() { return m_bInSquelchMode; } void SetOrUpdateBounds( vgui::Panel *pPanel, int left, int top, int wide, int tall, bool bOnlyUpdateBounds, int &topCoord, int &bottomCoord ) { if ( bOnlyUpdateBounds ) { if ( top < topCoord ) topCoord = top; if ( (top+tall) >= bottomCoord ) bottomCoord = top+tall; } else { pPanel->SetBounds( left, top, wide, tall ); } } //----------------------------------------------------------------------------- // Purpose: returns true if the target client has been banned // Input : playerID - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CVoiceStatus::IsPlayerBlocked(int iPlayer) { player_info_t pi; if ( !engine->GetPlayerInfo( iPlayer, &pi ) ) return false; return m_BanMgr.GetPlayerBan( pi.guid ); } //----------------------------------------------------------------------------- // Purpose: returns true if the player can't hear the other client due to game rules (eg. the other team) // Input : playerID - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CVoiceStatus::IsPlayerAudible(int iPlayer) { return !!m_AudiblePlayers[iPlayer-1]; } //----------------------------------------------------------------------------- // returns true if the player is currently speaking //----------------------------------------------------------------------------- bool CVoiceStatus::IsPlayerSpeaking(int iPlayerIndex) { return m_VoicePlayers[iPlayerIndex-1] != 0; } //----------------------------------------------------------------------------- // returns true if the local player is attempting to speak //----------------------------------------------------------------------------- bool CVoiceStatus::IsLocalPlayerSpeaking( void ) { #ifdef VOICE_VOX_ENABLE if ( voice_vox.GetBool() ) { if ( m_bAboveThresholdTimer.IsElapsed() == true ) { return false; } } #endif // VOICE_VOX_ENABLE return m_bTalking; } //----------------------------------------------------------------------------- // Purpose: blocks/unblocks the target client from being heard // Input : playerID - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- void CVoiceStatus::SetPlayerBlockedState(int iPlayer, bool blocked) { if (voice_clientdebug.GetInt()) { Msg( "CVoiceStatus::SetPlayerBlockedState part 1\n" ); } player_info_t pi; if ( !engine->GetPlayerInfo( iPlayer, &pi ) ) return; if (voice_clientdebug.GetInt()) { Msg( "CVoiceStatus::SetPlayerBlockedState part 2\n" ); } // Squelch or (try to) unsquelch this player. if (voice_clientdebug.GetInt()) { Msg("CVoiceStatus::SetPlayerBlockedState: setting player %d ban to %d\n", iPlayer, !m_BanMgr.GetPlayerBan(pi.guid)); } m_BanMgr.SetPlayerBan(pi.guid, !m_BanMgr.GetPlayerBan(pi.guid)); UpdateServerState(false); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CVoiceStatus::SetHeadLabelMaterial( const char *pszMaterial ) { if ( m_pHeadLabelMaterial ) { m_pHeadLabelMaterial->DecrementReferenceCount(); m_pHeadLabelMaterial = NULL; } m_pHeadLabelMaterial = materials->FindMaterial( pszMaterial, TEXTURE_GROUP_VGUI ); m_pHeadLabelMaterial->IncrementReferenceCount(); }