//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======// // // Purpose: // //===========================================================================// #include "mm_title_richpresence.h" #include "mm_title_contextvalues.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" static void SetAllUsersContext( DWORD dwContextId, DWORD dwValue, bool bAsync = true ) { #ifdef _X360 for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k ) { if ( XBX_GetUserIsGuest( k ) ) continue; int iCtrlr = XBX_GetUserId( k ); //if ( dwContextId == X_CONTEXT_PRESENCE ) DevMsg( "Set presence to %d for user %d\n", dwValue, iCtrlr ); if ( bAsync ) XUserSetContextEx( iCtrlr, dwContextId, dwValue, MMX360_NewOverlappedDormant() ); else XUserSetContext( iCtrlr, dwContextId, dwValue ); } #endif } static void SetAllUsersProperty( DWORD dwPropertyId, DWORD cbValue, void const *pvValue ) { #ifdef _X360 for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k ) { if ( XBX_GetUserIsGuest( k ) ) continue; int iCtrlr = XBX_GetUserId( k ); XUserSetPropertyEx( iCtrlr, dwPropertyId, cbValue, pvValue, MMX360_NewOverlappedDormant() ); } #endif } KeyValues * MM_Title_RichPresence_PrepareForSessionCreate( KeyValues *pSettings ) { SetAllUsersContext( X_CONTEXT_GAME_MODE, CONTEXT_GAME_MODE_CSS_GAME_MODE_MULTIPLAYER, false ); // matchmaking version { static int val; // must be valid for the async call extern ConVar mm_title_debug_version; val = mm_title_debug_version.GetInt(); SetAllUsersProperty( PROPERTY_CSS_MATCH_VERSION, sizeof( val ), &val ); DevMsg( "PrepareForSessionCreate: matchmaking version %d\n", val ); } return NULL; } void MM_Title_RichPresence_Update( KeyValues *pFullSettings, KeyValues *pUpdatedSettings ) { #if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS ) ( void ) g_pMatchExtensions->GetIBaseClientDLL()->GetRichPresenceStatusString(); #endif #ifdef _X360 if ( !pFullSettings ) { SetAllUsersContext( X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_MAINMENU ); // main menu return; } // Also set players information during initial rich presence update if ( !pUpdatedSettings && pFullSettings ) { MM_Title_RichPresence_PlayersChanged( pFullSettings ); // Open slots int numSlots = pFullSettings->GetInt( "members/numSlots", XBX_GetNumGameUsers() ); { static int val; // must be valid for the async call val = numSlots; SetAllUsersProperty( PROPERTY_CSS_OPEN_SLOTS, sizeof( val ), &val ); } // Team slots int numTeamSlots = MAX( pFullSettings->GetInt( "members/numTSlotsFree", 0 ), pFullSettings->GetInt( "members/numCTSlotsFree", 0 ) ); { static int val; // must be valid for the async call val = numTeamSlots; SetAllUsersProperty( PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS, sizeof( val ), &val ); } // Skill fields { static int val = 0; // must be valid for the async call SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_EXPERIENCE, sizeof( val ), &val ); SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL0, sizeof( val ), &val ); SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL1, sizeof( val ), &val ); SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL2, sizeof( val ), &val ); SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL3, sizeof( val ), &val ); SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL4, sizeof( val ), &val ); } // Listen/dedicated server resolver { static int val; // must be valid for the async call val = 0; extern ConVar mm_title_debug_dccheck; if ( mm_title_debug_dccheck.GetInt() ) val = ( ( mm_title_debug_dccheck.GetInt() > 0 ) ? 0 : 1 ); SetAllUsersProperty( PROPERTY_CSS_SEARCH_LISTEN_SERVER, sizeof( val ), &val ); } } // pUpdatedSettings = NULL when the session is created and all contexts need to be set KeyValues *pNewSettings = pUpdatedSettings ? pUpdatedSettings : pFullSettings; // if ( KeyValues *kvVal = pNewSettings->FindKey( "game/dlcrequired" ) ) // { // static int val[10]; // must be valid for the async call // uint64 uiDlcRequired = kvVal->GetUint64(); // extern ConVar mm_matchmaking_dlcsquery; // for ( int k = 1; k <= mm_matchmaking_dlcsquery.GetInt(); ++ k ) // { // val[k] = !!( uiDlcRequired & ( 1ull << k ) ); // DevMsg( "DLC%d required: %d\n", k, val[k] ); // SetAllUsersProperty( PROPERTY_REQUIRED_DLC1 - 1 + k, sizeof( val ), &val ); // } // } // Actual game type (classic, gungame) if ( char const *szGameType = pNewSettings->GetString( "game/type", NULL ) ) { SetAllUsersContext( CONTEXT_CSS_GAME_TYPE, g_GameTypeContexts->ScanValues( szGameType ) ); } // Game state if ( char const *szGameState = pNewSettings->GetString( "game/state", NULL ) ) { if ( !V_stricmp( pFullSettings->GetString( "system/network" ), "offline" ) ) SetAllUsersContext( CONTEXT_GAME_STATE, CONTEXT_GAME_STATE_SINGLE_PLAYER ); else SetAllUsersContext( CONTEXT_GAME_STATE, ( !V_stricmp( "game", szGameState ) ) ? CONTEXT_GAME_STATE_MULTIPLAYER : CONTEXT_GAME_STATE_IN_MENUS ); } // Actual game mode (casual, competitive, pro, etc) if ( char const *szValue = pNewSettings->GetString( "game/mode", NULL ) ) { SetAllUsersContext( CONTEXT_CSS_GAME_MODE, g_GameModeContexts->ScanValues( szValue ) ); static int val; // must be valid for the async call val = g_GameModeAsNumberContexts->ScanValues( szValue ); SetAllUsersProperty( PROPERTY_CSS_GAME_MODE_AS_NUMBER, sizeof( val ), &val ); } // MapGroup being used if ( char const *szMapGroupName = pNewSettings->GetString( "game/mapgroupname", NULL ) ) { SetAllUsersContext( CONTEXT_CSS_MAP_GROUP, g_MapGroupContexts->ScanValues( szMapGroupName ) ); } // Privacy if ( char const *szPrivacy = pNewSettings->GetString( "system/access", NULL ) ) { SetAllUsersContext( CONTEXT_CSS_PRIVACY, g_PrivacyContexts->ScanValues( szPrivacy ) ); } // Listen server if ( char const *szListenServer = pNewSettings->GetString( "server/server", NULL ) ) { static int val; // must be valid for the async call val = ( !V_stricmp( "listen", szListenServer ) ? 1 : 0 ); extern ConVar mm_title_debug_dccheck; if ( mm_title_debug_dccheck.GetInt() ) val = ( ( mm_title_debug_dccheck.GetInt() > 0 ) ? 0 : 1 ); SetAllUsersProperty( PROPERTY_CSS_SEARCH_LISTEN_SERVER, sizeof( val ), &val ); } // // Determine Rich Presence Display // if ( char const *szGameModeForRichPresence = pFullSettings->GetString( "game/mode", NULL ) ) { // Online/Offline if ( char const *szNetwork = pFullSettings->GetString( "system/network", NULL ) ) { DWORD dwLevelPresence = CONTEXT_PRESENCE_MAINMENU; if ( V_stricmp( "offline", szNetwork ) == 0 ) { dwLevelPresence = CONTEXT_PRESENCE_SINGLEPLAYER; } else if ( !V_stricmp( "lobby", pFullSettings->GetString( "game/state" ) ) ) { dwLevelPresence = CONTEXT_PRESENCE_LOBBY; } else { // Privacy if ( char const *szPrivacy = pFullSettings->GetString( "system/access", NULL ) ) { DWORD dwPrivacy = g_PrivacyContexts->ScanValues( szPrivacy ); if ( dwPrivacy == CONTEXT_CSS_PRIVACY_PUBLIC ) { // Public match // See if there are any free slots int numSlots = pFullSettings->GetInt( "members/numSlots", 0 ); int numPlayers = pFullSettings->GetInt( "members/numPlayers", 0 ); dwLevelPresence = (numSlots > numPlayers) ? CONTEXT_PRESENCE_MULTIPLAYER : CONTEXT_PRESENCE_MULTIPLAYER_NO_SLOTS; } else { // Private/invite only match dwLevelPresence = CONTEXT_PRESENCE_MULTIPLAYER_PRIVATE; } } } SetAllUsersContext( X_CONTEXT_PRESENCE, dwLevelPresence ); } // Update the map being used DWORD dwMapRichPresence = pFullSettings->GetInt( "game/mapRichPresence", 0xFFFF ); if ( dwMapRichPresence == 0xFFFF ) { // We didn't have a richpresence context set in GameModes.txt so look it up based on the name if ( char const *szMapName = pFullSettings->GetString( "game/map", NULL ) ) { dwMapRichPresence = g_LevelContexts->ScanValues( szMapName ); } } if ( dwMapRichPresence != 0xFFFF ) { SetAllUsersContext( CONTEXT_CSS_LEVEL, dwMapRichPresence ); } } #endif // _X360 } void MM_Title_RichPresence_PlayersChanged( KeyValues *pFullSettings ) { //#ifdef _X360 // // Set the installed DLCs masks // static int val[10]; // must be valid for the async call // uint64 uiDlcInstalled = g_pMatchFramework->GetMatchSystem()->GetDlcManager()->GetDataInfo()->GetUint64( "@info/installed" ); // extern ConVar mm_matchmaking_dlcsquery; // for ( int k = 1; k <= mm_matchmaking_dlcsquery.GetInt(); ++ k ) // { // val[k] = !!( uiDlcInstalled & ( 1ull << k ) ); // DevMsg( "DLC%d installed: %d\n", k, val[k] ); // SetAllUsersProperty( PROPERTY_INSTALLED_DLC1 - 1 + k, sizeof( val[k] ), &val[k] ); // } //#endif } // Called by the client to notify matchmaking that it should update matchmaking properties based // on player distribution among the teams. void MM_Title_RichPresence_UpdateTeamPropertiesCSGO( KeyValues *pCurrentSettings, KeyValues *pTeamProperties ) { #ifdef _X360 int numSlots = pCurrentSettings->GetInt( "members/numSlots", 0 ); int numPlayers = pTeamProperties->GetInt( "members/numPlayers", 0 ); int numSpectators = pTeamProperties->GetInt( "members/numSpectators", 0 ); int numExtraSpectatorSlots = pCurrentSettings->GetInt( "members/numExtraSpectatorSlots", 0 ); int numFreeTSlots = pCurrentSettings->GetInt( "members/numTSlotsFree", 0 ); int numFreeCTSlots = pCurrentSettings->GetInt( "members/numCTSlotsFree", 0 ); // Spectator overflow is computed in case we end up in a situation in which there are more // spectators than spectator slots. In that case, the extra spectators are counted against // the active player slots. int spectatorOverflow = numSpectators - numExtraSpectatorSlots; static int nPROPERTY_CSS_OPEN_SLOTS; nPROPERTY_CSS_OPEN_SLOTS = numSlots - numPlayers; if ( spectatorOverflow > 0 ) { nPROPERTY_CSS_OPEN_SLOTS = numSlots - ( numPlayers - numExtraSpectatorSlots + spectatorOverflow ); } SetAllUsersProperty( PROPERTY_CSS_OPEN_SLOTS, sizeof( nPROPERTY_CSS_OPEN_SLOTS ), &nPROPERTY_CSS_OPEN_SLOTS ); // post the average skill rank so matchmaking will work int avgRank = pTeamProperties->GetInt( "members/timeout", DEFAULT_NEW_PLAYER_ELO_RANK ); static int nPROPERTY_CSS_AGGREGATE_SKILL0; nPROPERTY_CSS_AGGREGATE_SKILL0 = avgRank; SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL0, sizeof( nPROPERTY_CSS_AGGREGATE_SKILL0 ), &nPROPERTY_CSS_AGGREGATE_SKILL0 ); // Post the maximum number of free slots on either team, for team matchmaking only static int nPROPERTY_CSS_MAX_OPEN_TEAM_SLOTS; nPROPERTY_CSS_MAX_OPEN_TEAM_SLOTS = max( numFreeTSlots, numFreeCTSlots ); SetAllUsersProperty( PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS, sizeof( nPROPERTY_CSS_MAX_OPEN_TEAM_SLOTS ), &nPROPERTY_CSS_MAX_OPEN_TEAM_SLOTS ); #elif defined( _PS3 ) // This is hacky: we stuff the rank into system data so that if lobby migrates // to a new owner and the metadata wasn't correctly set by the previous owner // then the new owner will set it if ( IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession() ) { KeyValues *kvSystemData = pMatchSession->GetSessionSystemData(); if ( !kvSystemData ) return; int avgRank = pTeamProperties->GetInt( "members/timeout", DEFAULT_NEW_PLAYER_ELO_RANK ); kvSystemData->SetInt( "timeout", avgRank ); int numOpenSlots = 10 - pTeamProperties->GetInt( "members/numPlayers" ); if ( numOpenSlots < 0 ) numOpenSlots = 0; kvSystemData->SetInt( "numOpenSlots", numOpenSlots ); char const *szSessionType = kvSystemData->GetString( "type", NULL ); DevMsg( "Session timeout value=%d (%s)\n", avgRank, szSessionType ? szSessionType : "offline" ); DevMsg( "Session numOpenSlots=%d (%s)\n", numOpenSlots, szSessionType ? szSessionType : "offline" ); if ( szSessionType && !Q_stricmp( szSessionType, "client" ) ) return; // don't run on clients for now, maybe later when we become owner steamapicontext->SteamMatchmaking()->SetLobbyData( kvSystemData->GetUint64( "xuidReserve", 0ull ), "game:timeout", CFmtStr( "%u", avgRank ) ); steamapicontext->SteamMatchmaking()->SetLobbyData( kvSystemData->GetUint64( "xuidReserve", 0ull ), "game:numOpenSlots", CFmtStr( "%u", numOpenSlots ) ); } #endif }