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.

1021 lines
35 KiB

  1. //===== Copyright � 1996-2009, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "mm_title.h"
  7. #include "mm_title_richpresence.h"
  8. #include "portal2.spa.h"
  9. #include "matchmaking/portal2/imatchext_portal2.h"
  10. #include "filesystem/ixboxinstaller.h"
  11. #include "filesystem.h"
  12. #include "vstdlib/random.h"
  13. #include "checksum_crc.h"
  14. #include "fmtstr.h"
  15. #ifndef _PS3
  16. #include <locale>
  17. #endif
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. ConVar mm_matchmaking_version( "mm_matchmaking_version", "9" );
  21. ConVar mm_matchmaking_dlcsquery( "mm_matchmaking_dlcsquery", "2" );
  22. class CMatchTitleGameSettingsMgr : public IMatchTitleGameSettingsMgr
  23. {
  24. public:
  25. // Extends server game details
  26. virtual void ExtendServerDetails( KeyValues *pDetails, KeyValues *pRequest );
  27. // Adds the essential part of game details to be broadcast
  28. virtual void ExtendLobbyDetailsTemplate( KeyValues *pDetails, char const *szReason, KeyValues *pFullSettings );
  29. // Extends game settings update packet for lobby transition,
  30. // either due to a migration or due to an endgame condition
  31. virtual void ExtendGameSettingsForLobbyTransition( KeyValues *pSettings, KeyValues *pSettingsUpdate, bool bEndGame );
  32. // Adds data for datacenter reporting
  33. virtual void ExtendDatacenterReport( KeyValues *pReportMsg, char const *szReason );
  34. // Rolls up game details for matches grouping
  35. virtual KeyValues * RollupGameDetails( KeyValues *pDetails, KeyValues *pRollup, KeyValues *pQuery );
  36. // Defines session search keys for matchmaking
  37. virtual KeyValues * DefineSessionSearchKeys( KeyValues *pSettings );
  38. // Defines dedicated server search key
  39. virtual KeyValues * DefineDedicatedSearchKeys( KeyValues *pSettings );
  40. // Initializes full game settings from potentially abbreviated game settings
  41. virtual void InitializeGameSettings( KeyValues *pSettings, char const *szReason );
  42. // Extends game settings update packet before it gets merged with
  43. // session settings and networked to remote clients
  44. virtual void ExtendGameSettingsUpdateKeys( KeyValues *pSettings, KeyValues *pUpdateDeleteKeys );
  45. // Prepares system for session creation
  46. virtual KeyValues * PrepareForSessionCreate( KeyValues *pSettings );
  47. // Executes the command on the session settings, this function on host
  48. // is allowed to modify Members/Game subkeys and has to fill in modified players KeyValues
  49. // When running on a remote client "ppPlayersUpdated" is NULL and players cannot
  50. // be modified
  51. virtual void ExecuteCommand( KeyValues *pCommand, KeyValues *pSessionSystemData, KeyValues *pSettings, KeyValues **ppPlayersUpdated );
  52. // Prepares the lobby for game or adjust settings of new players who
  53. // join a game in progress, this function is allowed to modify
  54. // Members/Game subkeys and has to fill in modified players KeyValues
  55. virtual void PrepareLobbyForGame( KeyValues *pSettings, KeyValues **ppPlayersUpdated );
  56. // Prepares the host team lobby for game adjusting the game settings
  57. // this function is allowed to prepare modification package to update
  58. // Game subkeys.
  59. // Returns the update/delete package to be applied to session settings
  60. // and pushed to dependent two sesssion of the two teams.
  61. virtual KeyValues * PrepareTeamLinkForGame( KeyValues *pSettingsLocal, KeyValues *pSettingsRemote );
  62. // Prepares the client lobby for migration
  63. // this function is called when the client session is still in the state
  64. // of "client" while handling the original host disconnection and decision
  65. // has been made that local machine will be elected as new "host"
  66. // Returns NULL if migration should proceed normally
  67. // Returns [ kvroot { "error" "n/a" } ] if migration should be aborted.
  68. virtual KeyValues * PrepareClientLobbyForMigration( KeyValues *pSettingsLocal, KeyValues *pMigrationInfo );
  69. // Prepares the session for server disconnect
  70. // this function is called when the session is still in the active gameplay
  71. // state and while localhost is handling the disconnection from game server.
  72. // Returns NULL to allow default flow
  73. // Returns [ kvroot { "disconnecthdlr" "<opt>" } ] where <opt> can be:
  74. // "destroy" : to trigger a disconnection error and destroy the session
  75. // "lobby" : to initiate a "salvaging" lobby transition
  76. virtual KeyValues * PrepareClientLobbyForGameDisconnect( KeyValues *pSettingsLocal, KeyValues *pDisconnectInfo );
  77. // Validates if client profile can set a stat or get awarded an achievement
  78. virtual bool AllowClientProfileUpdate( KeyValues *kvUpdate );
  79. };
  80. CMatchTitleGameSettingsMgr g_MatchTitleGameSettingsMgr;
  81. IMatchTitleGameSettingsMgr *g_pIMatchTitleGameSettingsMgr = &g_MatchTitleGameSettingsMgr;
  82. //
  83. // Mission information block
  84. //
  85. // Transfer mission info keys from mission manifest into key values
  86. #define NO_MISSION_INFO
  87. #ifdef NO_MISSION_INFO
  88. // Portal 2 doesn't have map versioning like L4D
  89. #define TransferMissionInformationToInfo( ... ) ((void)0)
  90. #else
  91. static void TransferMissionInformationToInfo( char const *szMissionName, KeyValues *pInfo )
  92. {
  93. KeyValues *pNewMission = NULL;
  94. if ( !pInfo )
  95. return;
  96. if ( !szMissionName || !*szMissionName )
  97. {
  98. pInfo->SetString( "version", pNewMission->GetString( "version", "1" ) );
  99. #ifndef _X360
  100. pInfo->SetString( "builtin", pNewMission->GetString( "builtin", "1" ) );
  101. pInfo->SetString( "displaytitle", pNewMission->GetString( "displaytitle", "#L4D360UI_Campaign_Any" ) );
  102. pInfo->SetString( "author", pNewMission->GetString( "author", "" ) );
  103. pInfo->SetString( "website", pNewMission->GetString( "website", "" ) );
  104. #endif
  105. return;
  106. }
  107. // Determine the new MissionInfo
  108. /*
  109. if ( KeyValues *pAllMissions = g_pMatchExtSwarm->GetAllMissions() )
  110. {
  111. pNewMission = pAllMissions->FindKey( szMissionName );
  112. }
  113. */
  114. pInfo->SetString( "version", pNewMission->GetString( "version", "" ) );
  115. #ifndef _X360
  116. pInfo->SetString( "builtin", pNewMission->GetString( "builtin", "1" ) );
  117. pInfo->SetString( "displaytitle", pNewMission->GetString( "displaytitle", "" ) );
  118. pInfo->SetString( "author", pNewMission->GetString( "author", "" ) );
  119. pInfo->SetString( "website", pNewMission->GetString( "website", "" ) );
  120. #endif
  121. }
  122. #endif
  123. // Determine the DLC mask for the current settings
  124. static uint64 DetermineDlcRequiredMask( KeyValues *pAggregateSettings )
  125. {
  126. if ( !Q_stricmp( pAggregateSettings->GetString( "game/mode" ), "coop_challenge" ) ||
  127. !Q_stricmp( pAggregateSettings->GetString( "options/play" ), "challenge" ) )
  128. return PORTAL2_DLCID_RETAIL_DLC1;
  129. return 0;
  130. }
  131. //
  132. // Implementation of CMatchTitleGameSettingsMgr
  133. //
  134. // Adds the essential part of game details to be broadcast
  135. void CMatchTitleGameSettingsMgr::ExtendLobbyDetailsTemplate( KeyValues *pDetails, char const *szReason, KeyValues *pFullSettings )
  136. {
  137. static KeyValues *pkvExt = KeyValues::FromString(
  138. "settings",
  139. " game { "
  140. " map #empty# "
  141. " mode #empty# "
  142. " state #empty# "
  143. " mp_coop_start #empty# "
  144. #ifndef NO_MISSION_INFO
  145. // Portal 2 doesn't have map versioning like L4D
  146. " missioninfo { "
  147. " version #empty# "
  148. #ifndef _X360
  149. " builtin #empty# "
  150. " displaytitle #empty# "
  151. " author #empty# "
  152. " website #empty# "
  153. #endif
  154. #endif // NO_MISSION_INFO
  155. " } "
  156. " } "
  157. );
  158. pDetails->MergeFrom( pkvExt, KeyValues::MERGE_KV_UPDATE );
  159. if ( szReason && !Q_stricmp( szReason, "reserve" ) )
  160. {
  161. // Single player reservation might need to pass the savegame name
  162. if ( char const *szSaveGame = pFullSettings->GetString( "game/save", NULL ) )
  163. {
  164. pDetails->SetString( "game/save", szSaveGame );
  165. }
  166. // For coop reservation we have to analyze all DLCs of the session machines
  167. // and set special keys for the server reservation
  168. const char *szGameMode = pFullSettings->GetString( "game/mode" );
  169. if ( !V_stricmp( szGameMode, "coop" ) || !V_stricmp( szGameMode, "coop_challenge" ) )
  170. {
  171. uint64 uiDLCor = pFullSettings->GetUint64( "members/machine0/dlcmask" ) | pFullSettings->GetUint64( "members/machine1/dlcmask" );
  172. pDetails->SetInt( "gameextras/skins", ( uiDLCor & PORTAL2_DLCID_COOP_BOT_SKINS ) ? 1 : -1 );
  173. pDetails->SetInt( "gameextras/helmets", ( uiDLCor & PORTAL2_DLCID_COOP_BOT_HELMETS ) ? 1 : -1 );
  174. pDetails->SetInt( "gameextras/antenna", ( uiDLCor & PORTAL2_DLCID_COOP_BOT_ANTENNA ) ? 1 : -1 );
  175. }
  176. }
  177. }
  178. // Extends server game details
  179. void CMatchTitleGameSettingsMgr::ExtendServerDetails( KeyValues *pDetails, KeyValues *pRequest )
  180. {
  181. // Query server info
  182. INetSupport::ServerInfo_t si;
  183. g_pMatchExtensions->GetINetSupport()->GetServerInfo( &si );
  184. // Determine map name
  185. char const *szMapName = si.m_szMapName;
  186. if ( !si.m_bActive )
  187. {
  188. if ( IVEngineClient *pIVEngineClient = g_pMatchExtensions->GetIVEngineClient() )
  189. {
  190. szMapName = pIVEngineClient->GetLevelNameShort();
  191. }
  192. }
  193. if ( !szMapName )
  194. szMapName = "";
  195. // Server is always in game
  196. pDetails->SetString( "game/state", "game" );
  197. //
  198. // Set game mode based on convars
  199. //
  200. pDetails->SetString( "game/mode", "coop" );
  201. //
  202. // Determine game campaign and map info
  203. //
  204. pDetails->SetString( "game/map", szMapName );
  205. //
  206. // Determine required dlc mask
  207. //
  208. pDetails->SetUint64( "game/dlcrequired", DetermineDlcRequiredMask( pDetails ) );
  209. }
  210. // Extends game settings update packet for lobby transition,
  211. // either due to a migration or due to an endgame condition
  212. void CMatchTitleGameSettingsMgr::ExtendGameSettingsForLobbyTransition( KeyValues *pSettings, KeyValues *pSettingsUpdate, bool bEndGame )
  213. {
  214. pSettingsUpdate->SetString( "game/state", "lobby" );
  215. }
  216. // Adds data for datacenter reporting
  217. void CMatchTitleGameSettingsMgr::ExtendDatacenterReport( KeyValues *cmd, char const *szReason )
  218. {
  219. #ifdef _X360
  220. if ( XBX_GetPrimaryUserId() == XBX_INVALID_USER_ID )
  221. return;
  222. if ( !XBX_GetNumGameUsers() || XBX_GetPrimaryUserIsGuest() )
  223. return;
  224. IPlayerLocal *pLocalPlayer = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() );
  225. if ( !pLocalPlayer )
  226. return;
  227. // Achievements info
  228. uint64 uiAchMask1 = 0; // 100 bits for achievements
  229. uint64 uiAchMask2 = 0; // 27 bits for asset awards
  230. {
  231. KeyValues *kvAchInfo = new KeyValues( "", "@achievements", 0, "@awards", 0 );
  232. KeyValues::AutoDelete autodelete_kvAchInfo( kvAchInfo );
  233. pLocalPlayer->GetAwardsData( kvAchInfo );
  234. for ( KeyValues *val = kvAchInfo->FindKey( "@achievements" )->GetFirstValue(); val; val = val->GetNextValue() )
  235. {
  236. int iVal = val->GetInt( "", 0 );
  237. if ( iVal <= 0 )
  238. continue;
  239. else if ( iVal < 64 )
  240. uiAchMask1 |= ( 1ull << iVal );
  241. else if ( iVal <= 100 )
  242. uiAchMask2 |= ( 1ull << ( iVal - 64 ) );
  243. }
  244. for ( KeyValues *val = kvAchInfo->FindKey( "@awards" )->GetFirstValue(); val; val = val->GetNextValue() )
  245. {
  246. int iVal = val->GetInt( "", 0 );
  247. if ( iVal <= 0 )
  248. continue;
  249. else if ( iVal < ( 128 - 100 ) )
  250. uiAchMask2 |= ( 1ull << ( iVal + 100 - 64 ) );
  251. }
  252. }
  253. cmd->SetUint64( "ach1", uiAchMask1 );
  254. cmd->SetUint64( "ach2", uiAchMask2 );
  255. if ( !V_stricmp( szReason, "datarequest" ) )
  256. {
  257. // Add game information
  258. TitleData1 * td1 = ( TitleData1 * ) pLocalPlayer->GetPlayerTitleData( TitleDataFieldsDescription_t::DB_TD1 );
  259. TitleData3 * td3 = ( TitleData3 * ) pLocalPlayer->GetPlayerTitleData( TitleDataFieldsDescription_t::DB_TD3 );
  260. // sp progress
  261. cmd->SetInt( "map_s", td1->uiSinglePlayerProgressChapter );
  262. // coop completion
  263. int numMapBitsFields = sizeof( td1->coop.mapbits ) / sizeof( uint64 );
  264. for ( int imap = 0; imap < numMapBitsFields; ++ imap )
  265. {
  266. cmd->SetUint64( CFmtStr( "map_%d", imap ), ( reinterpret_cast< uint64 * >( td1->coop.mapbits ) )[imap] );
  267. }
  268. if ( td3->cvUser.version )
  269. {
  270. // profile settings
  271. cmd->SetFloat( "cfg_pitch", td3->cvUser.joy_pitchsensitivity );
  272. cmd->SetFloat( "cfg_yaw", td3->cvUser.joy_yawsensitivity );
  273. cmd->SetInt( "cfg_joy", td3->cvUser.joy_cfg_preset );
  274. cmd->SetInt( "cfg_bit", td3->cvUser.bitfields[0] );
  275. }
  276. if ( td3->cvSystem.version )
  277. {
  278. // audio/video
  279. cmd->SetFloat( "sys_vol", td3->cvSystem.volume );
  280. cmd->SetFloat( "sys_mus", td3->cvSystem.snd_musicvolume );
  281. cmd->SetFloat( "sys_gam", td3->cvSystem.mat_monitorgamma );
  282. cmd->SetInt( "sys_ssm", td3->cvSystem.ss_splitmode );
  283. cmd->SetInt( "sys_bit", td3->cvSystem.bitfields[0] );
  284. }
  285. if ( g_pXboxInstaller )
  286. {
  287. cmd->SetInt( "inst",
  288. ( g_pXboxInstaller->IsFullyInstalled() ? 1 : 0 ) |
  289. ( g_pXboxInstaller->IsInstallEnabled() ? 2 : 0 )
  290. );
  291. }
  292. }
  293. #endif
  294. }
  295. // Adds a lowercased string into crc
  296. void AppendToRollup( char const *sz, CRC32_t &u )
  297. {
  298. if ( !sz )
  299. return;
  300. char const *p1 = sz;
  301. char const *p2 = p1;
  302. while ( *p2 )
  303. {
  304. while ( *p2 && !isupper( *p2 ) )
  305. {
  306. ++ p2;
  307. }
  308. if ( p2 > p1 )
  309. {
  310. CRC32_ProcessBuffer( &u, p1, p2 - p1 );
  311. }
  312. if ( *p2 )
  313. {
  314. char ch = tolower( *p2 );
  315. ++ p2;
  316. CRC32_ProcessBuffer( &u, &ch, sizeof( ch ) );
  317. }
  318. p1 = p2;
  319. }
  320. }
  321. // Rolls up game details for matches grouping
  322. KeyValues * CMatchTitleGameSettingsMgr::RollupGameDetails( KeyValues *pDetails, KeyValues *pRollup, KeyValues *pQuery )
  323. {
  324. if ( !pDetails && !pRollup )
  325. return NULL;
  326. MEM_ALLOC_CREDIT();
  327. if ( !pDetails )
  328. {
  329. // Check if the rollup is for a not installed addon with website
  330. if ( pRollup->GetInt( "game/missioninfo/builtin", 0 ) )
  331. return NULL; // builtin, not interested
  332. char const *szWebsite = pRollup->GetString( "game/missioninfo/website", NULL );
  333. if ( !szWebsite || !*szWebsite )
  334. return NULL; // no website, not interested
  335. int const nInstalledVersion = 1; // g_pMatchExtSwarm->GetAllMissions()->GetInt(
  336. // CFmtStr( "%s/version", pRollup->GetString( "game/campaign" ) ), -1 );
  337. if ( pRollup->GetInt( "game/missioninfo/version", 0 ) <= nInstalledVersion )
  338. return NULL; // addon installed with same or newer version, not interested
  339. // Zero out games information
  340. if ( KeyValues *pAgg = pRollup->FindKey( "rollup" ) )
  341. {
  342. pRollup->RemoveSubKey( pAgg );
  343. pAgg->deleteThis();
  344. }
  345. return pRollup;
  346. }
  347. if ( !pRollup )
  348. {
  349. // Determine the game mode
  350. char const *szGameMode = pDetails->GetString( "game/mode" );
  351. if ( !szGameMode || !*szGameMode )
  352. return NULL;
  353. // Determine campaign name
  354. char const *szCampaign = pDetails->GetString( "game/map" );
  355. if ( !szCampaign || !*szCampaign )
  356. return NULL;
  357. // Prepare the rollup
  358. pRollup = new KeyValues( "rollup" );
  359. CRC32_t uRollupKey = 0;
  360. CRC32_Init( &uRollupKey );
  361. // Game/mode
  362. pRollup->SetString( "game/mode", szGameMode );
  363. AppendToRollup( szGameMode, uRollupKey );
  364. // Game/campaign
  365. pRollup->SetString( "game/map", szCampaign );
  366. AppendToRollup( szCampaign, uRollupKey );
  367. // Game/state, only if queried for specific game state
  368. if ( char const *szGameState = pQuery->GetString( "game/state", NULL ) )
  369. {
  370. char const *szState = pDetails->GetString( "game/state", NULL );
  371. if ( szState && *szState )
  372. {
  373. pRollup->SetString( "game/state", szState );
  374. // AppendToRollup( szState, uRollupKey ); <- state doesn't affect the rollup
  375. }
  376. }
  377. // Let the aggregation system know the key
  378. pRollup->SetUint64( "rollupkey", uRollupKey );
  379. return pRollup;
  380. }
  381. // We need to rollup the formula
  382. if ( pDetails && pRollup )
  383. {
  384. // Add the rollup mission info
  385. int iVersion = pRollup->GetInt( "game/missioninfo/version", -1 ), iVersionD = pDetails->GetInt( "game/missioninfo/version" );
  386. if ( iVersionD > iVersion )
  387. {
  388. pRollup->FindKey( "game/missioninfo", true )->MergeFrom( pDetails->FindKey( "game/missioninfo" ), KeyValues::MERGE_KV_UPDATE );
  389. }
  390. // Add the rollup formula
  391. char const *szGameState = pDetails->GetString( "game/state", "game" );
  392. if ( Q_stricmp( szGameState, "lobby" ) && Q_stricmp( szGameState, "game" ) )
  393. szGameState = "game"; // force all other states like "finale" to be game
  394. CFmtStr strRollupKey( "rollup/%s", szGameState );
  395. pRollup->SetInt( strRollupKey, 1 + pRollup->GetInt( strRollupKey ) );
  396. // Rolled up
  397. return pRollup;
  398. }
  399. return NULL;
  400. }
  401. // Defines dedicated server search key
  402. KeyValues * CMatchTitleGameSettingsMgr::DefineDedicatedSearchKeys( KeyValues *pSettings )
  403. {
  404. // No dedicated servers support
  405. return NULL;
  406. }
  407. // Helper function to set filter for BuiltIn criteria on PC
  408. static void DefineSessionSearchKeys_BuiltIn( KeyValues *pSettings, KeyValues *pSearchKeys )
  409. {
  410. return; // don't set additional keys for Portal 2
  411. MEM_ALLOC_CREDIT();
  412. char const *szValue = pSettings->GetString( "game/missioninfo/builtin", NULL );
  413. // Check if we need to force "official" setting
  414. char const *szCampaign = pSettings->GetString( "game/campaign" );
  415. if ( !*szCampaign && IsPC() )
  416. {
  417. // Any campaign - candidate for "official" restriction
  418. char const *szAction = pSettings->GetString( "options/action" );
  419. if ( !Q_stricmp( szAction, "quickmatch" ) ||
  420. !Q_stricmp( szAction, "custommatch" ) )
  421. {
  422. szValue = "official"; // matchmaking on "ANY" = "official"
  423. }
  424. // Team matchmaking also means "official"
  425. if ( StringHasPrefix( pSettings->GetString( "game/mode" ), "team" ) )
  426. {
  427. szValue = "official";
  428. }
  429. }
  430. if ( szValue )
  431. {
  432. // Different values mean different search criteria:
  433. if ( !Q_stricmp( szValue, "addon" ) )
  434. pSearchKeys->SetInt( "Filter=/game:missioninfo:builtin", 0 );
  435. else if ( !Q_stricmp( szValue, "official" ) )
  436. pSearchKeys->SetInt( "Filter=/game:missioninfo:builtin", 1 );
  437. else if ( !Q_stricmp( szValue, "installedaddon" ) )
  438. pSearchKeys->SetInt( "Filter=/game:missioninfo:builtin", 0 );
  439. else if ( !Q_stricmp( szValue, "notinstalledaddon" ) )
  440. {
  441. pSearchKeys->SetInt( "Filter=/game:missioninfo:builtin", 0 );
  442. /*
  443. KeyValues *pAllMissions = g_pMatchExtSwarm->GetAllMissions();
  444. for ( KeyValues *pMission = pAllMissions ? pAllMissions->GetFirstTrueSubKey() : NULL; pMission; pMission = pMission->GetNextTrueSubKey() )
  445. {
  446. if ( !pMission->GetBool( "builtin" ) )
  447. pSearchKeys->FindKey( "Filter<>", true )->AddSubKey(
  448. new KeyValues( "game:campaign", NULL, pMission->GetString( "name" ) ) );
  449. }
  450. */
  451. pSearchKeys->SetString( "Filter<>/game:missioninfo:website", "" );
  452. }
  453. }
  454. }
  455. // Defines session search keys for matchmaking
  456. KeyValues * CMatchTitleGameSettingsMgr::DefineSessionSearchKeys( KeyValues *pSettings )
  457. {
  458. MEM_ALLOC_CREDIT();
  459. KeyValues *pResult = new KeyValues( "SessionSearch" );
  460. pResult->SetInt( "numPlayers", pSettings->GetInt( "members/numPlayers", XBX_GetNumGameUsers() ) );
  461. if ( IsX360() )
  462. {
  463. if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
  464. {
  465. static ContextValue_t values[] = {
  466. { "coop", SESSION_MATCH_QUERY_COOP },
  467. { "coop_challenge", SESSION_MATCH_QUERY_COOP },
  468. { NULL, 0xFFFF },
  469. };
  470. pResult->SetInt( "rule", values->ScanValues( szValue ) );
  471. }
  472. // Set the matchmaking version
  473. pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_MMVERSION ), mm_matchmaking_version.GetInt() );
  474. #ifdef _X360
  475. // Set the installed DLCs masks
  476. uint64 uiDlcsMask = g_pMatchFramework->GetMatchSystem()->GetDlcManager()->GetDataInfo()->GetUint64( "@info/installed" );
  477. for ( int k = 1; k <= mm_matchmaking_dlcsquery.GetInt(); ++ k )
  478. {
  479. pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_INSTALLED_DLC1 - 1 + k ), !!( uiDlcsMask & ( 1ull << k ) ) );
  480. }
  481. pResult->SetInt( "dlc1", PROPERTY_MMVERSION + 1 );
  482. pResult->SetInt( "dlcN", PROPERTY_MMVERSION + mm_matchmaking_dlcsquery.GetInt() );
  483. #endif
  484. // X_CONTEXT_GAME_TYPE
  485. pResult->SetInt( CFmtStr( "Contexts/%d", X_CONTEXT_GAME_TYPE ), X_CONTEXT_GAME_TYPE_STANDARD );
  486. // X_CONTEXT_GAME_MODE
  487. if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
  488. {
  489. pResult->SetInt( CFmtStr( "Contexts/%d", X_CONTEXT_GAME_MODE ), g_pcv_CONTEXT_GAME_MODE->ScanValues( szValue ) );
  490. }
  491. }
  492. else
  493. {
  494. if ( char const *szValue = pSettings->GetString( "game/state", NULL ) )
  495. {
  496. pResult->SetString( "Filter=/game:state", szValue );
  497. }
  498. if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
  499. {
  500. pResult->SetString( "Filter=/game:mode", szValue );
  501. }
  502. // Set the mp_coop_start search criteria if requested
  503. if ( KeyValues *kv_mp_coop_start = pSettings->FindKey( "game/mp_coop_start" ) )
  504. {
  505. pResult->SetInt( "Near/game:mp_coop_start", kv_mp_coop_start->GetInt() );
  506. }
  507. // When matchmaking on Steam, only matchmake within same platform (PS3-PS3 or PC-PC)
  508. if ( char const *szPlatform = pSettings->GetString( "game/platform", NULL ) )
  509. {
  510. pResult->SetString( "Filter=/game:platform", szPlatform );
  511. }
  512. // BuiltIn search keys
  513. DefineSessionSearchKeys_BuiltIn( pSettings, pResult );
  514. }
  515. return pResult;
  516. }
  517. #ifdef _DEBUG
  518. ConVar mm_test_slots( "mm_test_slots", "0", FCVAR_DEVELOPMENTONLY, "Force the game to support a different number of max slots.\n" );
  519. #endif
  520. template < typename T >
  521. static T AggregatePolicyAvg( int iCount, T iValue, T const *iArg )
  522. {
  523. if ( iArg )
  524. return iValue + *iArg;
  525. else
  526. return ( iCount > 0 ) ? ( iValue / iCount ) : iValue;
  527. }
  528. template < typename T >
  529. static T AggregatePolicyMax( int iCount, T iValue, T const *iArg )
  530. {
  531. if ( iArg )
  532. return MAX( iValue, *iArg );
  533. else
  534. return iValue;
  535. }
  536. template < typename T >
  537. static T AggregatePolicyMin( int iCount, T iValue, T const *iArg )
  538. {
  539. if ( iArg )
  540. return MIN( iValue, *iArg );
  541. else
  542. return iValue;
  543. }
  544. template < typename T >
  545. static T AggregateMembersField( KeyValues *pGameSettings, char const *szField, T iDefault, T ( *pfnPolicy )( int iCount, T iValue, T const *iArg ), T (KeyValues::*pfnGet)( const char *szKey, T getDefault ) )
  546. {
  547. int iCount = 0;
  548. T iAggregate = iDefault;
  549. for ( int iMachine = 0, numMachines = pGameSettings->GetInt( "members/numMachines" ); iMachine < numMachines; ++ iMachine )
  550. {
  551. KeyValues *pMachine = pGameSettings->FindKey( CFmtStr( "members/machine%d", iMachine ) );
  552. for ( int iPlayer = 0, numPlayers = pMachine->GetInt( "numPlayers" ); iPlayer < numPlayers; ++ iPlayer )
  553. {
  554. KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", iPlayer ) );
  555. if ( !pPlayer )
  556. continue;
  557. T iField = (pPlayer->*pfnGet)( CFmtStr( "game/%s", szField ), iDefault );
  558. iAggregate = (*pfnPolicy)( iCount, iAggregate, &iField );
  559. ++ iCount;
  560. }
  561. }
  562. return (*pfnPolicy)( iCount, iAggregate, NULL );
  563. }
  564. void UpdateAggregateMembersSettings( KeyValues *pFullGameSettings, KeyValues *pUpdate )
  565. {
  566. bool bSplitscreen = ( XBX_GetNumGameUsers() > 1 );
  567. pUpdate->SetInt( "game/mp_coop_start", AggregateMembersField<int>( pFullGameSettings, "mp_coop_start", bSplitscreen ? 0 : 1,
  568. bSplitscreen ? (int (*)(int,int,int const*))AggregatePolicyMax<int> : (int (*)(int,int,int const*))AggregatePolicyMin<int>,
  569. &KeyValues::GetInt ) );
  570. }
  571. void InitializeDlcMachineSettings( KeyValues *pSettings, KeyValues *pMachine )
  572. {
  573. if ( !pMachine )
  574. return; // don't have required machine to update
  575. IDlcManager *pDlcManager = g_pMatchFramework->GetMatchSystem()->GetDlcManager();
  576. pDlcManager->RequestDlcUpdate();
  577. pDlcManager->IsDlcUpdateFinished( true ); // force synchronous update
  578. uint64 uiDlcMaskDiscovered = pDlcManager->GetDataInfo()->GetUint64( "@info/installed" );
  579. uiDlcMaskDiscovered &= PORTAL2_DLC_ALLMASK;
  580. DevMsg( "InitializeDlcMachineSettings: [mask:0x%llX]\n", uiDlcMaskDiscovered );
  581. if ( uiDlcMaskDiscovered )
  582. {
  583. pMachine->SetUint64( "dlcmask", pMachine->GetUint64( "dlcmask" ) | uiDlcMaskDiscovered );
  584. }
  585. }
  586. void InitializeMemberSettings( KeyValues *pSettings )
  587. {
  588. IPlayerLocal *playerPrimary = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() );
  589. if ( !playerPrimary )
  590. return;
  591. //
  592. // Find local machine in session settings
  593. //
  594. KeyValues *pMachine = NULL;
  595. KeyValues *pPlayer = SessionMembersFindPlayer( pSettings, playerPrimary->GetXUID(), &pMachine );
  596. if ( !pMachine )
  597. return;
  598. // Initialize DLC for the machine
  599. InitializeDlcMachineSettings( pSettings, pMachine );
  600. //
  601. // Set title-specific data
  602. //
  603. for ( int jj = 0; jj < XBX_GetNumGameUsers(); ++ jj )
  604. {
  605. int iController = XBX_GetUserId( jj );
  606. IPlayerLocal *player = g_pPlayerManager->GetLocalPlayer( iController );
  607. if ( !player )
  608. continue;
  609. pPlayer = pMachine->FindKey( CFmtStr( "player%d", jj ) );
  610. if ( !pPlayer )
  611. continue;
  612. // Set mp_coop_start
  613. TitleData1 const td = * ( TitleData1 const * ) player->GetPlayerTitleData( 0 );
  614. COMPILE_TIME_ASSERT( TitleData1::CoopData_t::mp_coop_start < 8 );
  615. pPlayer->SetInt( "game/mp_coop_start", ( td.coop.mapbits[0] != 0 || td.coop.mapbits[1] != 0 ) ); // As long as user completed any of the first 64 maps consider his training complete
  616. }
  617. }
  618. // Initializes full game settings from potentially abbreviated game settings
  619. void CMatchTitleGameSettingsMgr::InitializeGameSettings( KeyValues *pSettings, char const *szReason )
  620. {
  621. if ( !Q_stricmp( pSettings->GetString( "system/netflag" ), "teamlink" ) )
  622. // No configuration on teamlinks
  623. return;
  624. InitializeMemberSettings( pSettings );
  625. if ( !Q_stricmp( szReason, "client" ) )
  626. // For client session it is sufficient to just initialize member settings
  627. return;
  628. // For hosts and searches we need to set member aggregates
  629. UpdateAggregateMembersSettings( pSettings, pSettings );
  630. if ( StringHasPrefix( szReason, "search" ) )
  631. // No additional configuration on search queries required
  632. return;
  633. MEM_ALLOC_CREDIT();
  634. char const *szNetwork = pSettings->GetString( "system/network", "LIVE" );
  635. if ( KeyValues *kv = pSettings->FindKey( "game", true ) )
  636. {
  637. kv->SetString( "state", "lobby" );
  638. KeyValuesAddDefaultString( kv, "mode", "coop" );
  639. KeyValuesAddDefaultString( kv, "map", "default" );
  640. }
  641. // Setup the mission info keys
  642. TransferMissionInformationToInfo(
  643. pSettings->GetString( "game/map" ),
  644. pSettings->FindKey( "game/missioninfo", true ) );
  645. pSettings->SetUint64( "game/dlcrequired", DetermineDlcRequiredMask( pSettings ) );
  646. // Offline games don't need slots and player setup
  647. if ( !Q_stricmp( "offline", szNetwork ) )
  648. return;
  649. //
  650. // Set the number of slots
  651. //
  652. int numSlots = Q_stricmp( pSettings->GetString( "game/mode", "coop" ), "sp" ) ? 2 : 1;
  653. #ifdef _DEBUG
  654. if ( int nForceDebugSlots = mm_test_slots.GetInt() )
  655. numSlots = nForceDebugSlots;
  656. #endif
  657. pSettings->SetInt( "members/numSlots", numSlots );
  658. }
  659. // Extends game settings update packet before it gets merged with
  660. // session settings and networked to remote clients
  661. void CMatchTitleGameSettingsMgr::ExtendGameSettingsUpdateKeys( KeyValues *pSettings, KeyValues *pUpdateDeleteKeys )
  662. {
  663. MEM_ALLOC_CREDIT();
  664. // Check if the map key is deleted
  665. if ( pUpdateDeleteKeys->FindKey( "delete/game/map" ) )
  666. {
  667. pUpdateDeleteKeys->SetString( "delete/game/missioninfo", "delete" );
  668. }
  669. // Check if the campaign key is modified
  670. if ( char const *szNewMission = pUpdateDeleteKeys->GetString( "update/game/map", NULL ) )
  671. {
  672. TransferMissionInformationToInfo( szNewMission,
  673. pUpdateDeleteKeys->FindKey( "update/game/missioninfo", true ) );
  674. }
  675. // Check if the campaign or chapter key is modified
  676. if ( pUpdateDeleteKeys->GetString( "update/game/map" ) )
  677. {
  678. KeyValues *pAggregateSettings = pSettings->MakeCopy();
  679. KeyValues::AutoDelete autodelete( pAggregateSettings );
  680. pAggregateSettings->MergeFrom( pUpdateDeleteKeys );
  681. uint64 uiDlcMaskRequired = DetermineDlcRequiredMask( pAggregateSettings );
  682. if ( uiDlcMaskRequired != pAggregateSettings->GetUint64( "game/dlcrequired" ) )
  683. pUpdateDeleteKeys->SetUint64( "update/game/dlcrequired", uiDlcMaskRequired );
  684. }
  685. }
  686. // Prepares system for session creation
  687. KeyValues * CMatchTitleGameSettingsMgr::PrepareForSessionCreate( KeyValues *pSettings )
  688. {
  689. return MM_Title_RichPresence_PrepareForSessionCreate( pSettings );
  690. }
  691. // Prepares the lobby for game or adjust settings of new players who
  692. // join a game in progress, this function is allowed to modify
  693. // Members/Game subkeys and has to write modified players XUIDs
  694. void CMatchTitleGameSettingsMgr::PrepareLobbyForGame( KeyValues *pSettings, KeyValues **ppPlayersUpdated )
  695. {
  696. IMatchSession *pSession = g_pMatchFramework->GetMatchSession();
  697. if ( !pSession )
  698. return;
  699. KeyValues *kvUpdate = new KeyValues( "update" );
  700. KeyValues::AutoDelete autodelete_kvUpdate( kvUpdate );
  701. // Set the flag to prevent players leave tracked while game is in progress
  702. kvUpdate->SetString( "update/system/netflag", "noleave" );
  703. // Figure out if we want to start in mp_coop_start or mp_coop_lobby_3 (or mp_coop_lobby_2 for consoles missing DLC)?
  704. char const *szMap = pSettings->GetString( "game/map" );
  705. char const *szGameMode = pSettings->GetString( "game/mode" );
  706. if ( !Q_stricmp( szGameMode, "coop" ) || !Q_stricmp( szGameMode, "coop_challenge" ) )
  707. {
  708. // Challenge mode never goes to start map
  709. bool bLobby = ( Q_stricmp( szGameMode, "coop_challenge" ) == 0 || pSettings->GetInt( "game/mp_coop_start" ) != 0 );
  710. char const *szMapRequired = bLobby ? "mp_coop_lobby_3" : "mp_coop_start";
  711. if ( IsGameConsole() && bLobby )
  712. {
  713. if ( !( ( pSettings->GetUint64( "members/machine0/dlcmask" ) & PORTAL2_DLCID_RETAIL_DLC1 ) &&
  714. ( XBX_GetNumGameUsers() > 1 ||
  715. ( pSettings->GetUint64( "members/machine1/dlcmask" ) & PORTAL2_DLCID_RETAIL_DLC1 ) ) ) )
  716. {
  717. // One of the players doesn't have the DLC!
  718. szMapRequired = "mp_coop_lobby_2";
  719. }
  720. }
  721. if ( Q_stricmp( szMapRequired, szMap ) && !Q_stricmp( szMap, "default" ) ) // map set to default, figure out where to start
  722. {
  723. // Need to start the game on a different map
  724. if ( ( pSession->GetSessionSettings() == pSettings ) && !Q_stricmp( "lobby", pSettings->GetString( "game/state" ) ) )
  725. {
  726. kvUpdate->SetString( "update/game/map", szMapRequired );
  727. }
  728. }
  729. }
  730. else if ( !Q_stricmp( szGameMode, "sp" ) )
  731. {
  732. if ( !Q_stricmp( szMap, "default" ) )
  733. {
  734. // Need to start the game on the first sp map
  735. if ( ( pSession->GetSessionSettings() == pSettings ) && !Q_stricmp( "lobby", pSettings->GetString( "game/state" ) ) )
  736. {
  737. kvUpdate->SetString( "update/game/map", "sp_a1_intro1" );
  738. }
  739. }
  740. }
  741. // Commit the update
  742. pSession->UpdateSessionSettings( kvUpdate );
  743. }
  744. // Prepares the host team lobby for game adjusting the game settings
  745. // this function is allowed to prepare modification package to update
  746. // Game subkeys.
  747. // Returns the update/delete package to be applied to session settings
  748. // and pushed to dependent two sesssion of the two teams.
  749. KeyValues * CMatchTitleGameSettingsMgr::PrepareTeamLinkForGame( KeyValues *pSettingsLocal, KeyValues *pSettingsRemote )
  750. {
  751. Assert( 0 );
  752. return NULL;
  753. }
  754. // Prepares the client lobby for migration
  755. // this function is called when the client session is still in the state
  756. // of "client" while handling the original host disconnection and decision
  757. // has been made that local machine will be elected as new "host"
  758. // Returns NULL if migration should proceed normally
  759. // Returns [ kvroot { "error" "n/a" } ] if migration should be aborted.
  760. KeyValues * CMatchTitleGameSettingsMgr::PrepareClientLobbyForMigration( KeyValues *pSettingsLocal, KeyValues *pMigrationInfo )
  761. {
  762. return new KeyValues( "disconnecthdrl", "error", "n/a" );
  763. }
  764. // Prepares the session for server disconnect
  765. // this function is called when the session is still in the active gameplay
  766. // state and while localhost is handling the disconnection from game server.
  767. // Returns NULL to allow default flow
  768. // Returns [ kvroot { "disconnecthdlr" "<opt>" } ] where <opt> can be:
  769. // "destroy" : to trigger a disconnection error and destroy the session
  770. // "lobby" : to initiate a "salvaging" lobby transition
  771. KeyValues * CMatchTitleGameSettingsMgr::PrepareClientLobbyForGameDisconnect( KeyValues *pSettingsLocal, KeyValues *pDisconnectInfo )
  772. {
  773. return new KeyValues( "disconnecthdrl", "disconnecthdlr", "destroy" );
  774. }
  775. // Validates if client profile can set a stat or get awarded an achievement
  776. bool CMatchTitleGameSettingsMgr::AllowClientProfileUpdate( KeyValues *kvUpdate )
  777. {
  778. IMatchSession *pIMatchSession = g_pMatchFramework->GetMatchSession();
  779. bool bAllowed = pIMatchSession && !pIMatchSession->GetSessionSettings()->GetBool( "game/sv_cheats" );
  780. if ( !bAllowed )
  781. {
  782. char const *szName = kvUpdate->GetString( "name" );
  783. if ( StringHasPrefix( szName, "GI.lesson." ) ||
  784. StringHasPrefix( szName, "CFG." ) ||
  785. StringHasPrefix( szName, "DLC." ) )
  786. {
  787. bAllowed = true; // allow achievement unrelated stuff to save with cheats on
  788. }
  789. }
  790. if ( !bAllowed )
  791. {
  792. Warning( "Stats and achievements are disabled: cheats turned on in this app session\n" );
  793. }
  794. return bAllowed;
  795. }
  796. static void OnRunCommand_PlayerInfo( KeyValues *pCommand, KeyValues *pSettings, KeyValues **ppPlayersUpdated )
  797. {
  798. MEM_ALLOC_CREDIT();
  799. XUID xuid = pCommand->GetUint64( "xuid" );
  800. KeyValues *kvGameNew = pCommand->FindKey( "game" );
  801. if ( !kvGameNew )
  802. return;
  803. KeyValues *pPlayer = SessionMembersFindPlayer( pSettings, xuid );
  804. if ( !pPlayer )
  805. return;
  806. KeyValues *kvGame = pPlayer->FindKey( "game", true );
  807. if ( !kvGame )
  808. return;
  809. // Merge in the client fields
  810. bool bModified = false;
  811. char const * arrClientFields[] = { "mp_coop_start" };
  812. for ( int jj = 0; jj < ARRAYSIZE( arrClientFields ); ++ jj )
  813. {
  814. char const *szVal = arrClientFields[jj];
  815. KeyValues *val = kvGameNew->FindKey( szVal );
  816. if ( !val )
  817. continue;
  818. int iValNew = val->GetInt();
  819. int iVal = kvGame->GetInt( szVal );
  820. if ( iValNew != iVal )
  821. {
  822. kvGame->SetInt( szVal, iValNew );
  823. bModified = true;
  824. }
  825. }
  826. if ( bModified )
  827. {
  828. * ( ppPlayersUpdated ++ ) = pPlayer;
  829. // Also recompute aggregate session fields
  830. IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession();
  831. if ( !pMatchSession )
  832. return;
  833. KeyValues *kvPackage = new KeyValues( "Update" );
  834. if ( KeyValues *kvUpdate = kvPackage->FindKey( "update", true ) )
  835. {
  836. UpdateAggregateMembersSettings( pMatchSession->GetSessionSettings(), kvUpdate );
  837. }
  838. pMatchSession->UpdateSessionSettings( KeyValues::AutoDeleteInline( kvPackage ) );
  839. }
  840. }
  841. // Executes the command on the session settings, this function on host
  842. // is allowed to modify Members/Game subkeys and has to fill in modified players KeyValues
  843. // When running on a remote client "ppPlayersUpdated" is NULL and players cannot
  844. // be modified
  845. void CMatchTitleGameSettingsMgr::ExecuteCommand( KeyValues *pCommand, KeyValues *pSessionSystemData, KeyValues *pSettings, KeyValues **ppPlayersUpdated )
  846. {
  847. char const *szCommand = pCommand->GetName();
  848. if ( !Q_stricmp( "Game::PlayerInfo", szCommand ) )
  849. {
  850. if ( !Q_stricmp( "host", pSessionSystemData->GetString( "type", "host" ) ) &&
  851. ppPlayersUpdated )
  852. {
  853. OnRunCommand_PlayerInfo( pCommand, pSettings, ppPlayersUpdated );
  854. return;
  855. }
  856. }
  857. }