//===== Copyright � 1996-2009, Valve Corporation, All rights reserved. ======// // // Purpose: // //===========================================================================// #include "mm_voice.h" #include "fmtstr.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #if defined( _GAMECONSOLE ) && !defined( _CERT ) ConVar mm_voice_fulldebug( "mm_voice_fulldebug", "0", FCVAR_DEVELOPMENTONLY ); #define MMVOICEMSG(...) if ( mm_voice_fulldebug.GetInt() > 0 ) { Msg( "[MMVOICE] " __VA_ARGS__ ); } #define MMVOICEMSG2(...) if ( mm_voice_fulldebug.GetInt() > 1 ) { Msg( "[MMVOICE] " __VA_ARGS__ ); } #else #define MMVOICEMSG(...) ((void)0) #define MMVOICEMSG2(...) ((void)0) #endif #if !defined(NO_STEAM) && !defined( SWDS ) static inline bool FriendRelationshipMute( int iRelationship ) { switch ( iRelationship ) { case k_EFriendRelationshipBlocked: case k_EFriendFlagIgnored: case k_EFriendFlagIgnoredFriend: return true; default: return false; } } #endif // // Construction/destruction // CMatchVoice::CMatchVoice() { ; } CMatchVoice::~CMatchVoice() { ; } static CMatchVoice g_MatchVoice; CMatchVoice *g_pMatchVoice = &g_MatchVoice; // // Implementation // // Whether remote player talking can be visualized / audible bool CMatchVoice::CanPlaybackTalker( XUID xuidTalker ) { if ( IsMachineMutingLocalTalkers( xuidTalker ) ) { MMVOICEMSG2( "CanPlaybackTalker(0x%llX)=false(IsMachineMutingLocalTalkers)\n", xuidTalker ); return false; } if ( IsMachineMuted( xuidTalker ) ) { MMVOICEMSG2( "CanPlaybackTalker(0x%llX)=false(IsMachineMuted)\n", xuidTalker ); return false; } return true; } // Whether we are explicitly muting a remote player bool CMatchVoice::IsTalkerMuted( XUID xuidTalker ) { #if defined( _PS3 ) && !defined( NO_STEAM ) if ( steamapicontext->SteamFriends()->GetUserRestrictions() ) { MMVOICEMSG( "IsTalkerMuted(0x%llX)=true(GetUserRestrictions)\n", xuidTalker ); return true; } #endif #if !defined(NO_STEAM) && !defined( SWDS ) if ( FriendRelationshipMute( steamapicontext->SteamFriends()->GetFriendRelationship( xuidTalker ) ) ) { MMVOICEMSG( "IsTalkerMuted(0x%llX)=true(GetFriendRelationship=0x%X)\n", xuidTalker, steamapicontext->SteamFriends()->GetFriendRelationship( xuidTalker ) ); return true; } if ( m_arrMutedTalkers.Find( xuidTalker ) != m_arrMutedTalkers.InvalidIndex() ) { MMVOICEMSG( "IsTalkerMuted(0x%llX)=true(locallist)\n", xuidTalker ); return true; } #endif #if defined( _GAMECONSOLE ) && !defined( _CERT ) XUID xuidOriginal = xuidTalker; xuidOriginal; #endif xuidTalker = RemapTalkerXuid( xuidTalker ); #if !defined(NO_STEAM) && !defined( SWDS ) if ( FriendRelationshipMute( steamapicontext->SteamFriends()->GetFriendRelationship( xuidTalker ) ) ) { MMVOICEMSG( "IsTalkerMuted(0x%llX/0x%llX)=true(GetFriendRelationship=0x%X)\n", xuidTalker, xuidOriginal, steamapicontext->SteamFriends()->GetFriendRelationship( xuidTalker ) ); return true; } #endif #ifdef _X360 if ( MMX360_GetUserCtrlrIndex( xuidTalker ) >= 0 ) // local players are never considered muted locally return false; for ( DWORD dwCtrlr = 0; dwCtrlr < XUSER_MAX_COUNT; ++ dwCtrlr ) { int iSlot = ( XBX_GetNumGameUsers() > 0 ) ? XBX_GetSlotByUserId( dwCtrlr ) : -1; if ( iSlot >= 0 && iSlot < ( int ) XBX_GetNumGameUsers() && XBX_GetUserIsGuest( iSlot ) ) continue; BOOL mutedInGuide = false; if ( ERROR_SUCCESS == g_pMatchExtensions->GetIXOnline()->XUserMuteListQuery( dwCtrlr, xuidTalker, &mutedInGuide ) && mutedInGuide ) { return true; } } #endif if ( m_arrMutedTalkers.Find( xuidTalker ) != m_arrMutedTalkers.InvalidIndex() ) { MMVOICEMSG( "IsTalkerMuted(0x%llX/0x%llX)=true(locallist)\n", xuidTalker, xuidOriginal ); return true; } return false; } // Whether we are muting any player on the player's machine bool CMatchVoice::IsMachineMuted( XUID xuidPlayer ) { #ifdef _X360 if ( MMX360_GetUserCtrlrIndex( xuidPlayer ) >= 0 ) // local players are never considered muted locally return false; // Find the session and the talker within session members IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession(); if ( !pMatchSession ) return IsTalkerMutedWithPrivileges( -1, xuidPlayer ); KeyValues *pSettings = pMatchSession->GetSessionSettings(); KeyValues *pMachine = NULL; KeyValues *pTalker = SessionMembersFindPlayer( pSettings, xuidPlayer, &pMachine ); if ( !pTalker || !pMachine ) return IsTalkerMutedWithPrivileges( -1, xuidPlayer ); // Walk all users from that machine int numPlayers = pMachine->GetInt( "numPlayers" ); for ( int k = 0; k < numPlayers; ++ k ) { KeyValues *pOtherPlayer = pMachine->FindKey( CFmtStr( "player%d", k ) ); if ( !pOtherPlayer ) continue; char const *szOtherName = pOtherPlayer->GetString( "name" ); if ( strchr( szOtherName, '(' ) ) continue; XUID xuidOther = pOtherPlayer->GetUint64( "xuid" ); if ( IsTalkerMutedWithPrivileges( -1, xuidOther ) ) return true; } return false; #else return IsTalkerMuted( xuidPlayer ); #endif } #ifdef _PS3 struct TalkerXuidRemap_t { XUID xuidSteamId; XUID xuidPsnId; }; #define TALKER_REMAP_CACHE_SIZE 4 static CUtlVector< TalkerXuidRemap_t > g_arrTalkerRemapCache( 0, TALKER_REMAP_CACHE_SIZE ); #endif // X360: Remap XUID of a player to a valid LIVE-enabled XUID // PS3: Remap SteamID of a player to a PSN ID XUID CMatchVoice::RemapTalkerXuid( XUID xuidTalker ) { if ( !IsGameConsole() ) return xuidTalker; #ifdef _PS3 for ( int k = 0; k < g_arrTalkerRemapCache.Count(); ++ k ) if ( g_arrTalkerRemapCache[k].xuidSteamId == xuidTalker ) return g_arrTalkerRemapCache[k].xuidPsnId; #endif // Find the session and the talker within session members IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession(); if ( !pMatchSession ) return xuidTalker; KeyValues *pSettings = pMatchSession->GetSessionSettings(); KeyValues *pMachine = NULL; KeyValues *pTalker = SessionMembersFindPlayer( pSettings, xuidTalker, &pMachine ); if ( !pTalker || !pMachine ) return xuidTalker; #ifdef _PS3 XUID xuidPsnId = pMachine->GetUint64( "psnid" ); if ( !xuidPsnId ) return xuidTalker; if ( g_arrTalkerRemapCache.Count() >= TALKER_REMAP_CACHE_SIZE ) g_arrTalkerRemapCache.SetCountNonDestructively( TALKER_REMAP_CACHE_SIZE - 1 ); TalkerXuidRemap_t txr = { xuidTalker, xuidPsnId }; g_arrTalkerRemapCache.AddToHead( txr ); return xuidPsnId; #endif // Check this user name if he is a guest char const *szTalkerName = pTalker->GetString( "name" ); char const *pchr = strchr( szTalkerName, '(' ); if ( !pchr ) return xuidTalker; // user is not a guest // Find another user from the same machine int numPlayers = pMachine->GetInt( "numPlayers" ); for ( int k = 0; k < numPlayers; ++ k ) { KeyValues *pOtherPlayer = pMachine->FindKey( CFmtStr( "player%d", k ) ); if ( !pOtherPlayer ) continue; char const *szOtherName = pOtherPlayer->GetString( "name" ); if ( strchr( szOtherName, '(' ) ) continue; XUID xuidOther = pOtherPlayer->GetUint64( "xuid" ); if ( xuidOther ) return xuidOther; } // No remapping return xuidTalker; } // Check player-player voice privileges for machine blocking purposes bool CMatchVoice::IsTalkerMutedWithPrivileges( int dwCtrlr, XUID xuidTalker ) { #ifdef _X360 if ( -1 == dwCtrlr ) // all controllers should be considered { for ( dwCtrlr = 0; dwCtrlr < XUSER_MAX_COUNT; ++ dwCtrlr ) { if ( IsTalkerMutedWithPrivileges( dwCtrlr, xuidTalker ) ) return true; } return false; } // Analyze this particular local controller against the given talker int iSlot = ( XBX_GetNumGameUsers() > 0 ) ? XBX_GetSlotByUserId( dwCtrlr ) : -1; if ( iSlot >= 0 && iSlot < ( int ) XBX_GetNumGameUsers() && XBX_GetUserIsGuest( iSlot ) ) // Guest has no say return false; XUSER_SIGNIN_INFO xsi; if ( ERROR_SUCCESS == XUserGetSigninInfo( dwCtrlr, XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) ) { if ( xsi.dwInfoFlags & XUSER_INFO_FLAG_GUEST ) // LIVE guests have no say return false; } BOOL mutedInGuide = false; if ( ERROR_SUCCESS == g_pMatchExtensions->GetIXOnline()->XUserMuteListQuery( dwCtrlr, xuidTalker, &mutedInGuide ) && mutedInGuide ) { return true; } // Check permissions to see if this player has friends-only or no communication set // Don't check permissions against other local players // Check for open privileges BOOL bHasPrivileges; DWORD dwResult = XUserCheckPrivilege( dwCtrlr, XPRIVILEGE_COMMUNICATIONS, &bHasPrivileges ); if ( dwResult == ERROR_SUCCESS ) { if ( !bHasPrivileges ) { // Second call checks for friends-only XUserCheckPrivilege( dwCtrlr, XPRIVILEGE_COMMUNICATIONS_FRIENDS_ONLY, &bHasPrivileges ); if ( bHasPrivileges ) { // Privileges are set to friends-only. See if the remote player is on our friends list. BOOL bIsFriend; dwResult = XUserAreUsersFriends( dwCtrlr, &xuidTalker, 1, &bIsFriend, NULL ); if ( dwResult != ERROR_SUCCESS || !bIsFriend ) { return true; } } else { // Privilege is nobody, mute them all return true; } } } #endif if ( m_arrMutedTalkers.Find( xuidTalker ) != m_arrMutedTalkers.InvalidIndex() ) { MMVOICEMSG( "IsTalkerMutedWithPrivileges(%d/0x%llX)=true(locallist)\n", dwCtrlr, xuidTalker ); return true; } return false; } // Check if player machine is muting any of local players bool CMatchVoice::IsMachineMutingLocalTalkers( XUID xuidPlayer ) { // Find the session and the talker within session members IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession(); if ( !pMatchSession ) return false; KeyValues *pSettings = pMatchSession->GetSessionSettings(); KeyValues *pMachine = NULL; SessionMembersFindPlayer( pSettings, xuidPlayer, &pMachine ); if ( !pMachine ) return false; // Find the local player record in the session XUID xuidLocalId = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID(); KeyValues *pLocalMachine = NULL; SessionMembersFindPlayer( pSettings, xuidLocalId, &pLocalMachine ); if ( !pLocalMachine || pLocalMachine == pMachine ) return false; int numLocalPlayers = pLocalMachine->GetInt( "numPlayers" ); // Check the mutelist on the machine if ( KeyValues *pMutelist = pMachine->FindKey( "Mutelist" ) ) { for ( KeyValues *val = pMutelist->GetFirstValue(); val; val = val->GetNextValue() ) { XUID xuidMuted = val->GetUint64(); if ( !xuidMuted ) continue; for ( int iLocal = 0; iLocal < numLocalPlayers; ++ iLocal ) { XUID xuidLocal = pLocalMachine->GetUint64( CFmtStr( "player%d/xuid", iLocal ) ); if ( xuidMuted == xuidLocal ) { MMVOICEMSG2( "IsMachineMutingLocalTalkers(0x%llX/0x%llX)=true(mutelist)\n", xuidPlayer, xuidLocal ); return true; } } } } return false; } // Whether voice recording mode is currently active bool CMatchVoice::IsVoiceRecording() { #if !defined(_X360) && !defined(NO_STEAM) && !defined( SWDS ) #ifdef _PS3 EVoiceResult res = steamapicontext->SteamUser()->GetAvailableVoice( NULL, NULL, 11025 ); #else EVoiceResult res = steamapicontext->SteamUser()->GetAvailableVoice( NULL, NULL, 0 ); #endif switch ( res ) { case k_EVoiceResultOK: case k_EVoiceResultNoData: return true; default: return false; } #endif return false; } // Enable or disable voice recording void CMatchVoice::SetVoiceRecording( bool bRecordingEnabled ) { #if !defined(_X360) && !defined(NO_STEAM) && !defined( SWDS ) if ( bRecordingEnabled ) steamapicontext->SteamUser()->StartVoiceRecording(); else steamapicontext->SteamUser()->StopVoiceRecording(); #endif } // Enable or disable voice mute for a given talker void CMatchVoice::MuteTalker( XUID xuidTalker, bool bMute ) { #if !defined(_X360) && !defined(NO_STEAM) && !defined( SWDS ) if ( !xuidTalker ) { if ( !bMute ) m_arrMutedTalkers.Purge(); } else { m_arrMutedTalkers.FindAndFastRemove( xuidTalker ); if ( bMute ) { m_arrMutedTalkers.AddToTail( xuidTalker ); } } g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnSysMuteListChanged" ) ); #endif } CON_COMMAND( voice_reset_mutelist, "Reset all mute information for all players who were ever muted." ) { g_pMatchVoice->MuteTalker( 0ull, false ); Msg( "Mute list cleared.\n" ); } #if !defined( _X360 ) && !defined( NO_STEAM ) CON_COMMAND( voice_mute, "Mute a specific Steam user" ) { if ( args.ArgC() != 2 ) { goto usage; } else { int iUserId = Q_atoi( args.Arg( 1 ) ); player_info_t pi; if ( !g_pMatchExtensions->GetIVEngineClient()->GetPlayerInfo( iUserId, &pi ) || !pi.xuid ) { Msg( "Player# is invalid or refers to a bot, please use \"voice_show_mute\" command.\n" ); goto usage; } g_pMatchVoice->MuteTalker( pi.xuid, true ); if ( !g_pMatchExtensions->GetIVEngineClient()->GetDemoPlaybackParameters() ) { Msg( "%s is now muted.\n", pi.name ); } return; } usage: Msg( "Example usage: voice_mute player# - where player# is a number that you can find with \"voice_show_mute\" command.\n" ); } CON_COMMAND( voice_unmute, "Unmute a specific Steam user, or `all` to unmute all connected players." ) { if ( args.ArgC() != 2 ) { goto usage; } else { if ( !Q_stricmp( "all", args.Arg(1) ) ) { XUID xuidLocal = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID(); int maxClients = g_pMatchExtensions->GetIVEngineClient()->GetMaxClients(); for ( int i = 1; i <= maxClients; ++ i ) { // Get the player info from the engine player_info_t pi; if ( !g_pMatchExtensions->GetIVEngineClient()->GetPlayerInfo( i, &pi ) ) continue; if ( !pi.xuid ) continue; if ( pi.xuid == xuidLocal ) continue; g_pMatchVoice->MuteTalker( pi.xuid, false ); } Msg( "All connected players have been unmuted.\n" ); return; } int iUserId = Q_atoi( args.Arg( 1 ) ); player_info_t pi; if ( !g_pMatchExtensions->GetIVEngineClient()->GetPlayerInfo( iUserId, &pi ) || !pi.xuid ) { Msg( "Player# is invalid or refers to a bot, please use \"voice_show_mute\" command.\n" ); goto usage; } g_pMatchVoice->MuteTalker( pi.xuid, false ); if ( !g_pMatchExtensions->GetIVEngineClient()->GetDemoPlaybackParameters() ) { Msg( "%s is now unmuted.\n", pi.name ); } return; } usage: Msg( "Example usage: voice_unmute {player#|all} - where player# is a number that you can find with \"voice_show_mute\" command, or all to unmute all connected players.\n" ); } CON_COMMAND( voice_show_mute, "Show whether current players are muted." ) { if ( g_pMatchExtensions->GetIVEngineClient()->GetDemoPlaybackParameters() ) return; bool bPrinted = false; XUID xuidLocal = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID(); int maxClients = g_pMatchExtensions->GetIVEngineClient()->GetMaxClients(); for ( int i = 1; i <= maxClients; ++ i ) { // Get the player info from the engine player_info_t pi; if ( !g_pMatchExtensions->GetIVEngineClient()->GetPlayerInfo( i, &pi ) ) continue; if ( !pi.xuid ) continue; if ( pi.xuid == xuidLocal ) continue; if ( !bPrinted ) { bPrinted = true; Msg( "Player# Player Name\n" ); Msg( "------- ----------------\n" ); } Msg( " % 2d %s %s\n", i, g_pMatchVoice->IsTalkerMuted( pi.xuid ) ? "(muted)" : " ", pi.name ); } if ( bPrinted ) { Msg( "------- ----------------\n" ); } else { Msg( "No players currently connected who can be muted.\n" ); } } #endif