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.

1076 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 "swarm.spa.h"
  9. #include "matchext_swarm.h"
  10. #include "vstdlib/random.h"
  11. #include "checksum_crc.h"
  12. #include "fmtstr.h"
  13. #include <locale>
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. ConVar mm_matchmaking_version( "mm_matchmaking_version", "9" );
  17. ConVar mm_matchmaking_dlcsquery( "mm_matchmaking_dlcsquery", "2" );
  18. class CMatchTitleGameSettingsMgr : public IMatchTitleGameSettingsMgr
  19. {
  20. public:
  21. // Extends server game details
  22. virtual void ExtendServerDetails( KeyValues *pDetails, KeyValues *pRequest );
  23. // Adds the essential part of game details to be broadcast
  24. virtual void ExtendLobbyDetailsTemplate( KeyValues *pDetails, char const *szReason, KeyValues *pFullSettings );
  25. // Extends game settings update packet for lobby transition,
  26. // either due to a migration or due to an endgame condition
  27. virtual void ExtendGameSettingsForLobbyTransition( KeyValues *pSettings, KeyValues *pSettingsUpdate, bool bEndGame );
  28. // Rolls up game details for matches grouping
  29. virtual KeyValues * RollupGameDetails( KeyValues *pDetails, KeyValues *pRollup, KeyValues *pQuery );
  30. // Defines session search keys for matchmaking
  31. virtual KeyValues * DefineSessionSearchKeys( KeyValues *pSettings );
  32. // Defines dedicated server search key
  33. virtual KeyValues * DefineDedicatedSearchKeys( KeyValues *pSettings );
  34. // Initializes full game settings from potentially abbreviated game settings
  35. virtual void InitializeGameSettings( KeyValues *pSettings );
  36. // Extends game settings update packet before it gets merged with
  37. // session settings and networked to remote clients
  38. virtual void ExtendGameSettingsUpdateKeys( KeyValues *pSettings, KeyValues *pUpdateDeleteKeys );
  39. // Prepares system for session creation
  40. virtual KeyValues * PrepareForSessionCreate( KeyValues *pSettings );
  41. // Executes the command on the session settings, this function on host
  42. // is allowed to modify Members/Game subkeys and has to fill in modified players KeyValues
  43. // When running on a remote client "ppPlayersUpdated" is NULL and players cannot
  44. // be modified
  45. virtual void ExecuteCommand( KeyValues *pCommand, KeyValues *pSessionSystemData, KeyValues *pSettings, KeyValues **ppPlayersUpdated );
  46. // Prepares the lobby for game or adjust settings of new players who
  47. // join a game in progress, this function is allowed to modify
  48. // Members/Game subkeys and has to fill in modified players KeyValues
  49. virtual void PrepareLobbyForGame( KeyValues *pSettings, KeyValues **ppPlayersUpdated );
  50. // Prepares the host team lobby for game adjusting the game settings
  51. // this function is allowed to prepare modification package to update
  52. // Game subkeys.
  53. // Returns the update/delete package to be applied to session settings
  54. // and pushed to dependent two sesssion of the two teams.
  55. virtual KeyValues * PrepareTeamLinkForGame( KeyValues *pSettingsLocal, KeyValues *pSettingsRemote );
  56. };
  57. CMatchTitleGameSettingsMgr g_MatchTitleGameSettingsMgr;
  58. IMatchTitleGameSettingsMgr *g_pIMatchTitleGameSettingsMgr = &g_MatchTitleGameSettingsMgr;
  59. //
  60. // Mission information block
  61. //
  62. // Transfer mission info keys from mission manifest into key values
  63. static void TransferMissionInformationToInfo( char const *szMissionName, KeyValues *pInfo )
  64. {
  65. KeyValues *pNewMission = NULL;
  66. if ( !pInfo )
  67. return;
  68. if ( !szMissionName || !*szMissionName )
  69. {
  70. pInfo->SetString( "version", pNewMission->GetString( "version", "1" ) );
  71. #ifndef _X360
  72. pInfo->SetString( "builtin", pNewMission->GetString( "builtin", "1" ) );
  73. pInfo->SetString( "displaytitle", pNewMission->GetString( "displaytitle", "#L4D360UI_Campaign_Any" ) );
  74. pInfo->SetString( "author", pNewMission->GetString( "author", "" ) );
  75. pInfo->SetString( "website", pNewMission->GetString( "website", "" ) );
  76. #endif
  77. return;
  78. }
  79. // Determine the new MissionInfo
  80. if ( KeyValues *pAllMissions = g_pMatchExtSwarm->GetAllMissions() )
  81. {
  82. pNewMission = pAllMissions->FindKey( szMissionName );
  83. }
  84. pInfo->SetString( "version", pNewMission->GetString( "version", "" ) );
  85. #ifndef _X360
  86. pInfo->SetString( "builtin", pNewMission->GetString( "builtin", "1" ) );
  87. pInfo->SetString( "displaytitle", pNewMission->GetString( "displaytitle", "" ) );
  88. pInfo->SetString( "author", pNewMission->GetString( "author", "" ) );
  89. pInfo->SetString( "website", pNewMission->GetString( "website", "" ) );
  90. #endif
  91. }
  92. // Determine the DLC mask for the current settings
  93. static uint64 DetermineDlcRequiredMask( KeyValues *pAggregateSettings )
  94. {
  95. KeyValues *pMissionInfo = NULL;
  96. KeyValues *pChapterInfo = g_pMatchExtSwarm->GetMapInfo( pAggregateSettings, &pMissionInfo );
  97. return
  98. pMissionInfo->GetUint64( "dlcmask" ) |
  99. pChapterInfo->GetUint64( "dlcmask" );
  100. }
  101. //
  102. // Implementation of CMatchTitleGameSettingsMgr
  103. //
  104. // Adds the essential part of game details to be broadcast
  105. void CMatchTitleGameSettingsMgr::ExtendLobbyDetailsTemplate( KeyValues *pDetails, char const *szReason, KeyValues *pFullSettings )
  106. {
  107. static KeyValues *pkvExt = KeyValues::FromString(
  108. "settings",
  109. " game { "
  110. " mode #empty# "
  111. " difficulty #empty# "
  112. " maxrounds #int#3 "
  113. " campaign #empty# "
  114. " chapter #int#1 "
  115. " state #empty# "
  116. " missioninfo { "
  117. " version #empty# "
  118. #ifndef _X360
  119. " builtin #empty# "
  120. " displaytitle #empty# "
  121. " author #empty# "
  122. " website #empty# "
  123. #endif
  124. " } "
  125. " } "
  126. );
  127. pDetails->MergeFrom( pkvExt, KeyValues::MERGE_KV_UPDATE );
  128. if ( szReason && !Q_stricmp( szReason, "reserve" ) )
  129. {
  130. // For the reservation we have to analyze all DLCs of the session machines
  131. // and set special keys for the server reservation
  132. // e.g.: pDetails->SetInt( "GameExtras/bat", -1 );
  133. }
  134. }
  135. // Extends server game details
  136. void CMatchTitleGameSettingsMgr::ExtendServerDetails( KeyValues *pDetails, KeyValues *pRequest )
  137. {
  138. // Query server info
  139. INetSupport::ServerInfo_t si;
  140. g_pMatchExtensions->GetINetSupport()->GetServerInfo( &si );
  141. // Determine map name
  142. char const *szMapName = si.m_szMapName;
  143. if ( !si.m_bActive )
  144. {
  145. if ( IVEngineClient *pIVEngineClient = g_pMatchExtensions->GetIVEngineClient() )
  146. {
  147. szMapName = pIVEngineClient->GetLevelNameShort();
  148. }
  149. }
  150. if ( !szMapName )
  151. szMapName = "";
  152. // Server is always in game
  153. pDetails->SetString( "game/state", "game" );
  154. //
  155. // Set game mode based on convars
  156. //
  157. static ConVarRef mp_gamemode( "mp_gamemode", true );
  158. if ( mp_gamemode.IsValid() )
  159. {
  160. pDetails->SetString( "game/mode", mp_gamemode.GetString() );
  161. }
  162. //
  163. // Determine game campaign and map info
  164. //
  165. KeyValues *pMissionInfo = NULL;
  166. if ( KeyValues *pMapInfo = g_pMatchExtSwarm->GetMapInfoByBspName( pDetails, szMapName, &pMissionInfo ) )
  167. {
  168. pDetails->SetString( "game/campaign", pMissionInfo->GetString( "name" ) );
  169. pDetails->SetInt( "game/chapter", pMapInfo->GetInt( "chapter" ) );
  170. // Setup the mission info keys
  171. TransferMissionInformationToInfo(
  172. pDetails->GetString( "game/campaign" ),
  173. pDetails->FindKey( "game/missioninfo", true ) );
  174. }
  175. //
  176. // Determine game difficulty
  177. //
  178. static ConVarRef ZombieDifficulty( "z_difficulty", true );
  179. if ( ZombieDifficulty.IsValid() )
  180. {
  181. pDetails->SetString( "game/difficulty", ZombieDifficulty.GetString() );
  182. }
  183. //
  184. // Determine max rounds
  185. //
  186. static ConVarRef r_mp_roundlimit( "mp_roundlimit", true );
  187. if ( r_mp_roundlimit.IsValid() )
  188. {
  189. pDetails->SetInt( "game/maxrounds", r_mp_roundlimit.GetInt() );
  190. }
  191. //
  192. // Determine required dlc mask
  193. //
  194. pDetails->SetUint64( "game/dlcrequired", DetermineDlcRequiredMask( pDetails ) );
  195. }
  196. // Extends game settings update packet for lobby transition,
  197. // either due to a migration or due to an endgame condition
  198. void CMatchTitleGameSettingsMgr::ExtendGameSettingsForLobbyTransition( KeyValues *pSettings, KeyValues *pSettingsUpdate, bool bEndGame )
  199. {
  200. pSettingsUpdate->SetString( "game/state", "lobby" );
  201. char const *szGameMode = pSettings->GetString( "game/mode", "coop" );
  202. szGameMode;
  203. if ( bEndGame )
  204. {
  205. bool bNoRollChapterTo1 = false; // !Q_stricmp( szGameMode, "survival" ) || !Q_stricmp( szGameMode, "scavenge" ) || !Q_stricmp( szGameMode, "teamscavenge" );
  206. if ( !bNoRollChapterTo1 )
  207. {
  208. pSettingsUpdate->SetInt( "game/chapter", 1 );
  209. }
  210. }
  211. }
  212. // Adds a lowercased string into crc
  213. void AppendToRollup( char const *sz, CRC32_t &u )
  214. {
  215. if ( !sz )
  216. return;
  217. char const *p1 = sz;
  218. char const *p2 = p1;
  219. while ( *p2 )
  220. {
  221. while ( *p2 && !isupper( *p2 ) )
  222. {
  223. ++ p2;
  224. }
  225. if ( p2 > p1 )
  226. {
  227. CRC32_ProcessBuffer( &u, p1, p2 - p1 );
  228. }
  229. if ( *p2 )
  230. {
  231. char ch = tolower( *p2 );
  232. ++ p2;
  233. CRC32_ProcessBuffer( &u, &ch, sizeof( ch ) );
  234. }
  235. p1 = p2;
  236. }
  237. }
  238. // Rolls up game details for matches grouping
  239. KeyValues * CMatchTitleGameSettingsMgr::RollupGameDetails( KeyValues *pDetails, KeyValues *pRollup, KeyValues *pQuery )
  240. {
  241. if ( !pDetails && !pRollup )
  242. return NULL;
  243. MEM_ALLOC_CREDIT();
  244. if ( !pDetails )
  245. {
  246. // Check if the rollup is for a not installed addon with website
  247. if ( pRollup->GetInt( "game/missioninfo/builtin", 0 ) )
  248. return NULL; // builtin, not interested
  249. char const *szWebsite = pRollup->GetString( "game/missioninfo/website", NULL );
  250. if ( !szWebsite || !*szWebsite )
  251. return NULL; // no website, not interested
  252. int const nInstalledVersion = g_pMatchExtSwarm->GetAllMissions()->GetInt(
  253. CFmtStr( "%s/version", pRollup->GetString( "game/campaign" ) ), -1 );
  254. if ( pRollup->GetInt( "game/missioninfo/version", 0 ) <= nInstalledVersion )
  255. return NULL; // addon installed with same or newer version, not interested
  256. // Zero out games information
  257. if ( KeyValues *pAgg = pRollup->FindKey( "rollup" ) )
  258. {
  259. pRollup->RemoveSubKey( pAgg );
  260. pAgg->deleteThis();
  261. }
  262. return pRollup;
  263. }
  264. if ( !pRollup )
  265. {
  266. // Determine the game mode
  267. char const *szGameMode = pDetails->GetString( "game/mode" );
  268. if ( !szGameMode || !*szGameMode )
  269. return NULL;
  270. // For chapter-based rollups
  271. bool bChapterBased = !Q_stricmp( "survival", szGameMode ) || !Q_stricmp( "scavenge", szGameMode );
  272. bool bDifficulty = !Q_stricmp( "coop", szGameMode ) || !Q_stricmp( "realism", szGameMode );
  273. // Determine campaign name
  274. char const *szCampaign = pDetails->GetString( "game/campaign" );
  275. if ( !szCampaign || !*szCampaign )
  276. return NULL;
  277. // Prepare the rollup
  278. pRollup = new KeyValues( "rollup" );
  279. CRC32_t uRollupKey = 0;
  280. CRC32_Init( &uRollupKey );
  281. // Game/mode
  282. pRollup->SetString( "game/mode", szGameMode );
  283. AppendToRollup( szGameMode, uRollupKey );
  284. // Game/campaign
  285. pRollup->SetString( "game/campaign", szCampaign );
  286. AppendToRollup( szCampaign, uRollupKey );
  287. // Game/chapter
  288. if ( bChapterBased )
  289. {
  290. int iChapter = pDetails->GetInt( "game/chapter", -1 );
  291. if ( iChapter > 0 )
  292. {
  293. pRollup->SetInt( "game/chapter", iChapter );
  294. CRC32_ProcessBuffer( &uRollupKey, &iChapter, sizeof( iChapter ) );
  295. }
  296. }
  297. // Game/difficulty
  298. if ( bDifficulty )
  299. {
  300. char const *szDifficulty = pDetails->GetString( "game/difficulty", NULL );
  301. if ( szDifficulty && *szDifficulty )
  302. {
  303. pRollup->SetString( "game/difficulty", szDifficulty );
  304. AppendToRollup( szDifficulty, uRollupKey );
  305. }
  306. }
  307. // Game/state, only if queried for specific game state
  308. if ( char const *szGameState = pQuery->GetString( "game/state", NULL ) )
  309. {
  310. char const *szState = pDetails->GetString( "game/state", NULL );
  311. if ( szState && *szState )
  312. {
  313. pRollup->SetString( "game/state", szState );
  314. // AppendToRollup( szState, uRollupKey ); <- state doesn't affect the rollup
  315. }
  316. }
  317. // Let the aggregation system know the key
  318. pRollup->SetUint64( "rollupkey", uRollupKey );
  319. return pRollup;
  320. }
  321. // We need to rollup the formula
  322. if ( pDetails && pRollup )
  323. {
  324. // Add the rollup mission info
  325. int iVersion = pRollup->GetInt( "game/missioninfo/version", -1 ), iVersionD = pDetails->GetInt( "game/missioninfo/version" );
  326. if ( iVersionD > iVersion )
  327. {
  328. pRollup->FindKey( "game/missioninfo", true )->MergeFrom( pDetails->FindKey( "game/missioninfo" ), KeyValues::MERGE_KV_UPDATE );
  329. }
  330. // Add the rollup formula
  331. char const *szGameState = pDetails->GetString( "game/state", "game" );
  332. if ( Q_stricmp( szGameState, "lobby" ) && Q_stricmp( szGameState, "game" ) )
  333. szGameState = "game"; // force all other states like "finale" to be game
  334. CFmtStr strRollupKey( "rollup/%s", szGameState );
  335. pRollup->SetInt( strRollupKey, 1 + pRollup->GetInt( strRollupKey ) );
  336. // Rolled up
  337. return pRollup;
  338. }
  339. return NULL;
  340. }
  341. // Defines dedicated server search key
  342. KeyValues * CMatchTitleGameSettingsMgr::DefineDedicatedSearchKeys( KeyValues *pSettings )
  343. {
  344. if ( IsPC() )
  345. {
  346. MEM_ALLOC_CREDIT();
  347. char const *szGameMode = pSettings->GetString( "game/mode", "coop" );
  348. if ( !szGameMode || !*szGameMode )
  349. szGameMode = "empty";
  350. char const *szMissionTag = NULL;
  351. KeyValues *pMissionInfo = NULL;
  352. if ( g_pMatchExtSwarm->GetMapInfo( pSettings, &pMissionInfo ) )
  353. {
  354. if ( !pMissionInfo->GetInt( "builtin" ) )
  355. {
  356. szMissionTag = pMissionInfo->GetString( "cfgtag" );
  357. }
  358. }
  359. static ConVarRef sv_search_key( "sv_search_key" );
  360. KeyValues *pKeys = new KeyValues( "SearchKeys" );
  361. pKeys->SetString( "gamedata", CFmtStr( szMissionTag ? "%s,key:%s%d,%s" : "%s,key:%s%d",
  362. szGameMode,
  363. sv_search_key.GetString(),
  364. g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber(),
  365. szMissionTag ) );
  366. return pKeys;
  367. }
  368. else
  369. {
  370. return NULL;
  371. }
  372. }
  373. // Helper function to set filter for BuiltIn criteria on PC
  374. static void DefineSessionSearchKeys_BuiltIn( KeyValues *pSettings, KeyValues *pSearchKeys )
  375. {
  376. MEM_ALLOC_CREDIT();
  377. char const *szValue = pSettings->GetString( "game/missioninfo/builtin", NULL );
  378. // Check if we need to force "official" setting
  379. char const *szCampaign = pSettings->GetString( "game/campaign" );
  380. if ( !*szCampaign && IsPC() )
  381. {
  382. // Any campaign - candidate for "official" restriction
  383. char const *szAction = pSettings->GetString( "options/action" );
  384. if ( !Q_stricmp( szAction, "quickmatch" ) ||
  385. !Q_stricmp( szAction, "custommatch" ) )
  386. {
  387. szValue = "official"; // matchmaking on "ANY" = "official"
  388. }
  389. // Team matchmaking also means "official"
  390. if ( StringHasPrefix( pSettings->GetString( "game/mode" ), "team" ) )
  391. {
  392. szValue = "official";
  393. }
  394. }
  395. if ( szValue )
  396. {
  397. // Different values mean different search criteria:
  398. if ( !Q_stricmp( szValue, "addon" ) )
  399. pSearchKeys->SetInt( "Filter=/game:missioninfo:builtin", 0 );
  400. else if ( !Q_stricmp( szValue, "official" ) )
  401. pSearchKeys->SetInt( "Filter=/game:missioninfo:builtin", 1 );
  402. else if ( !Q_stricmp( szValue, "installedaddon" ) )
  403. pSearchKeys->SetInt( "Filter=/game:missioninfo:builtin", 0 );
  404. else if ( !Q_stricmp( szValue, "notinstalledaddon" ) )
  405. {
  406. pSearchKeys->SetInt( "Filter=/game:missioninfo:builtin", 0 );
  407. KeyValues *pAllMissions = g_pMatchExtSwarm->GetAllMissions();
  408. for ( KeyValues *pMission = pAllMissions ? pAllMissions->GetFirstTrueSubKey() : NULL; pMission; pMission = pMission->GetNextTrueSubKey() )
  409. {
  410. if ( !pMission->GetBool( "builtin" ) )
  411. pSearchKeys->FindKey( "Filter<>", true )->AddSubKey(
  412. new KeyValues( "game:campaign", NULL, pMission->GetString( "name" ) ) );
  413. }
  414. pSearchKeys->SetString( "Filter<>/game:missioninfo:website", "" );
  415. }
  416. }
  417. }
  418. // Defines session search keys for matchmaking
  419. KeyValues * CMatchTitleGameSettingsMgr::DefineSessionSearchKeys( KeyValues *pSettings )
  420. {
  421. MEM_ALLOC_CREDIT();
  422. KeyValues *pResult = new KeyValues( "SessionSearch" );
  423. pResult->SetInt( "numPlayers", pSettings->GetInt( "members/numPlayers", XBX_GetNumGameUsers() ) );
  424. char const *szGameMode = pSettings->GetString( "game/mode", "" );
  425. if ( IsX360() )
  426. {
  427. if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
  428. {
  429. static ContextValue_t values[] = {
  430. { "versus", SESSION_MATCH_QUERY_PUBLIC_STATE_C___SORT___CHAPTER },
  431. { "teamversus", SESSION_MATCH_QUERY_TEAM_STATE_C_CHAPTER },
  432. { "scavenge", SESSION_MATCH_QUERY_PUBLIC_STATE_C_CHAPTER___SORT___ROUNDS },
  433. { "teamscavenge", SESSION_MATCH_QUERY_TEAM_STATE_C_CHAPTER_ROUNDS },
  434. { "survival", SESSION_MATCH_QUERY_PUBLIC_STATE_C_CHAPTER },
  435. { "coop", SESSION_MATCH_QUERY_PUBLIC_STATE_C_DIFF___SORT___CHAPTER },
  436. { "realism", SESSION_MATCH_QUERY_PUBLIC_STATE_C_DIFF___SORT___CHAPTER },
  437. { NULL, 0xFFFF },
  438. };
  439. pResult->SetInt( "rule", values->ScanValues( szValue ) );
  440. }
  441. // Set the matchmaking version
  442. pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_MMVERSION ), mm_matchmaking_version.GetInt() );
  443. #ifdef _X360
  444. // Set the installed DLCs masks
  445. uint64 uiDlcsMask = MatchSession_GetDlcInstalledMask();
  446. for ( int k = 1; k <= mm_matchmaking_dlcsquery.GetInt(); ++ k )
  447. {
  448. pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_MMVERSION + k ), !!( uiDlcsMask & ( 1ull << k ) ) );
  449. }
  450. pResult->SetInt( "dlc1", PROPERTY_MMVERSION + 1 );
  451. pResult->SetInt( "dlcN", PROPERTY_MMVERSION + mm_matchmaking_dlcsquery.GetInt() );
  452. #endif
  453. // X_CONTEXT_GAME_TYPE
  454. pResult->SetInt( CFmtStr( "Contexts/%d", X_CONTEXT_GAME_TYPE ), X_CONTEXT_GAME_TYPE_STANDARD );
  455. // X_CONTEXT_GAME_MODE
  456. if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
  457. {
  458. pResult->SetInt( CFmtStr( "Contexts/%d", X_CONTEXT_GAME_MODE ), g_pcv_CONTEXT_GAME_MODE->ScanValues( szValue ) );
  459. }
  460. if ( char const *szValue = pSettings->GetString( "game/state", NULL ) )
  461. {
  462. pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_STATE ), g_pcv_CONTEXT_STATE->ScanValues( szValue ) );
  463. }
  464. if ( char const *szValue = pSettings->GetString( "game/difficulty", NULL ) )
  465. {
  466. if ( !Q_stricmp( "coop", szGameMode ) || !Q_stricmp( "realism", szGameMode ) )
  467. {
  468. pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_DIFFICULTY ), g_pcv_CONTEXT_DIFFICULTY->ScanValues( szValue ) );
  469. }
  470. }
  471. if ( int val = pSettings->GetInt( "game/maxrounds" ) )
  472. {
  473. if ( !Q_stricmp( "scavenge", szGameMode ) || !Q_stricmp( "teamscavenge", szGameMode ) )
  474. {
  475. pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_MAXROUNDS ), val );
  476. }
  477. }
  478. char const *szCampaign = pSettings->GetString( "game/campaign" );
  479. if ( *szCampaign )
  480. {
  481. DWORD dwContext = CONTEXT_CAMPAIGN_UNKNOWN;
  482. if ( KeyValues *pAllMissions = g_pMatchExtSwarm->GetAllMissions() )
  483. {
  484. if ( KeyValues *pMission = pAllMissions->FindKey( szCampaign ) )
  485. {
  486. dwContext = pMission->GetInt( "x360ctx", dwContext );
  487. }
  488. }
  489. if ( dwContext != CONTEXT_CAMPAIGN_UNKNOWN )
  490. {
  491. pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_CAMPAIGN ), dwContext );
  492. }
  493. }
  494. if ( int val = pSettings->GetInt( "game/chapter" ) )
  495. {
  496. pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_CHAPTER ), val );
  497. }
  498. }
  499. else
  500. {
  501. char const *szGameMode = pSettings->GetString( "game/mode" );
  502. if ( char const *szValue = pSettings->GetString( "game/state", NULL ) )
  503. {
  504. pResult->SetString( "Filter=/game:state", szValue );
  505. }
  506. if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
  507. {
  508. pResult->SetString( "Filter=/game:mode", szValue );
  509. }
  510. if ( char const *szValue = pSettings->GetString( "game/difficulty", NULL ) )
  511. {
  512. pResult->SetString( "Filter=/game:difficulty", szValue );
  513. }
  514. if ( int val = pSettings->GetInt( "game/maxrounds" ) )
  515. {
  516. char const *szFilterType = NULL;
  517. if ( !Q_stricmp( "scavenge", szGameMode ) )
  518. szFilterType = "Near";
  519. else if ( !Q_stricmp( "teamscavenge", szGameMode ) )
  520. szFilterType = "Filter=";
  521. if ( szFilterType )
  522. pResult->SetInt( CFmtStr( "%s/game:maxrounds", szFilterType ), val );
  523. }
  524. char const *szCampaign = pSettings->GetString( "game/campaign" );
  525. if ( *szCampaign )
  526. {
  527. pResult->SetString( "Filter=/game:campaign", szCampaign );
  528. }
  529. if ( int val = pSettings->GetInt( "game/chapter" ) )
  530. {
  531. char const *szFilterType = "Near";
  532. if ( !Q_stricmp( "survival", szGameMode ) ||
  533. !Q_stricmp( "scavenge", szGameMode ) ||
  534. StringHasPrefix( szGameMode, "team" ) )
  535. szFilterType = "Filter=";
  536. pResult->SetInt( CFmtStr( "%s/game:chapter", szFilterType ), val );
  537. }
  538. // For all game modes (except team-on-team) prefer games with more players (e.g. prefer 7/8 game over 2/8 game)
  539. if ( !StringHasPrefix( szGameMode, "team" ) )
  540. {
  541. pResult->SetInt( "Near/members:numPlayers", 99 ); // passing in a random big number to cause descending sort order
  542. }
  543. // BuiltIn search keys
  544. DefineSessionSearchKeys_BuiltIn( pSettings, pResult );
  545. // For team-on-team game modes require dedicated/local servers preference to match
  546. if ( StringHasPrefix( szGameMode, "team" ) )
  547. {
  548. char const *szLocal = pSettings->GetString( "options/server" );
  549. char const *szFilter = Q_stricmp( szLocal, "listen" ) ? "Filter<>" : "Filter=";
  550. pResult->SetString( CFmtStr( "%s/options:server", szFilter ), "listen" );
  551. }
  552. }
  553. //
  554. // In case the search is quickmatch or custom match for
  555. // a game in progress, add a second search pass for games
  556. // in lobby. This way if the search doesn't find any games
  557. // in progress then it will at least find a lobby.
  558. //
  559. char const *szAction = pSettings->GetString( "options/action" );
  560. if ( ( !Q_stricmp( szAction, "quickmatch" ) ||
  561. !Q_stricmp( szAction, "custommatch" ) ) &&
  562. !Q_stricmp( "game", pSettings->GetString( "game/state", "" ) ) )
  563. {
  564. KeyValues *pNextSearchPass = pResult->MakeCopy();
  565. pNextSearchPass->SetName( "nextpass" );
  566. pResult->AddSubKey( pNextSearchPass );
  567. // When matchmaking for a game in progress allow lobbies to also be considered if no games in progress found
  568. if ( IsX360() )
  569. pNextSearchPass->SetInt( CFmtStr( "Contexts/%d", CONTEXT_STATE ), CONTEXT_STATE_LOBBY );
  570. else
  571. pNextSearchPass->SetString( "Filter=/game:state", "lobby" );
  572. }
  573. //
  574. // For team based game modes there are chances that a team link session
  575. // is not specifying a campaign/chapter, so we need to perform fallback
  576. // searches to ANY
  577. //
  578. if ( StringHasPrefix( szGameMode, "team" ) )
  579. {
  580. KeyValues *pLastPass = pResult;
  581. // If we specify a chapter, then try linking on ANY chapter
  582. if ( pSettings->GetInt( "game/chapter" ) )
  583. {
  584. KeyValues *pNextSearchPass = pLastPass->MakeCopy();
  585. pNextSearchPass->SetName( "nextpass" );
  586. pLastPass->AddSubKey( pNextSearchPass );
  587. pLastPass = pNextSearchPass;
  588. // Search for chapter = 0
  589. if ( IsX360() )
  590. pNextSearchPass->SetInt( CFmtStr( "Properties/%d", PROPERTY_CHAPTER ), 0 );
  591. else
  592. pNextSearchPass->SetInt( "Filter=/game:chapter", 0 );
  593. }
  594. // If we specify a campaign and that's a built-in campaign, then try linking on ANY campaign
  595. if ( *pSettings->GetString( "game/campaign" ) && ( IsX360() || pSettings->GetInt( "game/missioninfo/builtin" ) ) )
  596. {
  597. KeyValues *pNextSearchPass = pLastPass->MakeCopy();
  598. pNextSearchPass->SetName( "nextpass" );
  599. pLastPass->AddSubKey( pNextSearchPass );
  600. pLastPass = pNextSearchPass;
  601. // Search for campaign = ANY
  602. if ( IsX360() )
  603. pNextSearchPass->SetInt( CFmtStr( "Contexts/%d", CONTEXT_CAMPAIGN ), CONTEXT_CAMPAIGN_ANY );
  604. else
  605. pNextSearchPass->SetString( "Filter=/game:campaign", "" );
  606. }
  607. }
  608. return pResult;
  609. }
  610. #ifdef _DEBUG
  611. ConVar mm_test_slots( "mm_test_slots", "0", FCVAR_DEVELOPMENTONLY, "Force the game to support a different number of max slots.\n" );
  612. #endif
  613. // Initializes full game settings from potentially abbreviated game settings
  614. void CMatchTitleGameSettingsMgr::InitializeGameSettings( KeyValues *pSettings )
  615. {
  616. MEM_ALLOC_CREDIT();
  617. char const *szNetwork = pSettings->GetString( "system/network", "LIVE" );
  618. char const *szPlayOptions = pSettings->GetString( "options/play", "" );
  619. if ( KeyValues *kv = pSettings->FindKey( "game", true ) )
  620. {
  621. kv->SetString( "state", "lobby" );
  622. KeyValuesAddDefaultString( kv, "mode", "coop" );
  623. char const *szGameMode = kv->GetString( "mode" );
  624. // Allowing ANY campaign/chapter
  625. bool bAllowAnyChapter = false;
  626. if ( StringHasPrefix( szGameMode, "team" ) )
  627. bAllowAnyChapter = true;
  628. // Build a list of random campaigns (weighted by chapters for single-chapter game modes)
  629. bool bSingleChapterGameMode = !Q_stricmp( "survival", szGameMode ) || !Q_stricmp( "scavenge", szGameMode );
  630. CFmtStr sModeChapters( "modes/%s/chapters", szGameMode );
  631. CUtlVector< KeyValues * > arrBuiltinMissions;
  632. for ( KeyValues *pMission = g_pMatchExtSwarm->GetAllMissions()->GetFirstTrueSubKey(); pMission; pMission = pMission->GetNextTrueSubKey() )
  633. {
  634. if ( !pMission->GetInt( "builtin" ) )
  635. continue;
  636. if ( !Q_stricmp( "credits", pMission->GetString( "name" ) ) )
  637. continue;
  638. int numChapters = pMission->GetInt( sModeChapters );
  639. if ( !numChapters )
  640. continue;
  641. // Weigh missions proportionally to the number of chapters
  642. // if the game mode is a single chapter
  643. arrBuiltinMissions.AddToTail( pMission );
  644. if ( bSingleChapterGameMode )
  645. {
  646. for ( int k = 1; k < numChapters; ++ k )
  647. arrBuiltinMissions.AddToTail( pMission );
  648. }
  649. }
  650. // Random campaign generation if player was searching for "Any" campaign
  651. if ( !bAllowAnyChapter && !*kv->GetString( "campaign" ) && arrBuiltinMissions.Count() )
  652. {
  653. int iRandomMission = RandomInt( 0, arrBuiltinMissions.Count() - 1 );
  654. kv->SetString( "campaign", arrBuiltinMissions[ iRandomMission ]->GetString( "name" ) );
  655. }
  656. // In survival/scavenge mode we also randomly generate the chapter if "Any" was searched
  657. if ( !bAllowAnyChapter && !kv->GetInt( "chapter" ) )
  658. {
  659. if ( bSingleChapterGameMode )
  660. {
  661. int nChapters = g_pMatchExtSwarm->GetAllMissions()->GetInt( CFmtStr( "%s/modes/%s/chapters", kv->GetString( "campaign" ), szGameMode ), 1 );
  662. int nRandomChapter = RandomInt( 1, nChapters );
  663. kv->SetInt( "chapter", nRandomChapter );
  664. }
  665. else
  666. {
  667. kv->SetInt( "chapter", 1 );
  668. }
  669. }
  670. Assert( bAllowAnyChapter || g_pMatchExtSwarm->GetMapInfo( pSettings ) );
  671. KeyValuesAddDefaultString( kv, "difficulty", "normal" );
  672. KeyValuesAddDefaultValue( kv, "maxrounds", 3, SetInt );
  673. if ( !Q_stricmp( "offline", szNetwork ) && arrBuiltinMissions.Count() )
  674. {
  675. kv->SetString( "campaign", arrBuiltinMissions[0]->GetString( "name" ) );
  676. kv->SetInt( "chapter", 1 );
  677. }
  678. if ( !Q_stricmp( "commentary", szPlayOptions ) )
  679. {
  680. kv->SetString( "campaign", "L4D2C5" ); // Commentary is on C5
  681. kv->SetInt( "chapter", 1 );
  682. kv->SetString( "difficulty", "easy" );
  683. }
  684. // Credits are played on a dedicated map
  685. if ( !Q_stricmp( "credits", szPlayOptions ) )
  686. {
  687. kv->SetString( "campaign", "credits" );
  688. kv->SetInt( "chapter", 1 );
  689. kv->SetString( "difficulty", "easy" );
  690. }
  691. }
  692. // Setup the mission info keys
  693. TransferMissionInformationToInfo(
  694. pSettings->GetString( "game/campaign" ),
  695. pSettings->FindKey( "game/missioninfo", true ) );
  696. pSettings->SetUint64( "game/dlcrequired", DetermineDlcRequiredMask( pSettings ) );
  697. // Offline games don't need slots and player setup
  698. if ( !Q_stricmp( "offline", szNetwork ) )
  699. return;
  700. //
  701. // Set the number of slots
  702. //
  703. int numSlots = 4;
  704. const char *pszGameMode = pSettings->GetString( "game/mode", "" );
  705. if ( !Q_stricmp( "versus", pszGameMode ) || !Q_stricmp( "scavenge", pszGameMode ) )
  706. {
  707. numSlots = 8;
  708. }
  709. #ifdef _DEBUG
  710. if ( int nForceDebugSlots = mm_test_slots.GetInt() )
  711. numSlots = nForceDebugSlots;
  712. #endif
  713. pSettings->SetInt( "members/numSlots", numSlots );
  714. }
  715. // Extends game settings update packet before it gets merged with
  716. // session settings and networked to remote clients
  717. void CMatchTitleGameSettingsMgr::ExtendGameSettingsUpdateKeys( KeyValues *pSettings, KeyValues *pUpdateDeleteKeys )
  718. {
  719. MEM_ALLOC_CREDIT();
  720. // Check if the campaign key is deleted
  721. if ( pUpdateDeleteKeys->FindKey( "delete/game/campaign" ) )
  722. {
  723. pUpdateDeleteKeys->SetString( "delete/game/missioninfo", "delete" );
  724. }
  725. // Check if the campaign key is modified
  726. if ( char const *szNewMission = pUpdateDeleteKeys->GetString( "update/game/campaign", NULL ) )
  727. {
  728. TransferMissionInformationToInfo( szNewMission,
  729. pUpdateDeleteKeys->FindKey( "update/game/missioninfo", true ) );
  730. }
  731. // Check if the campaign or chapter key is modified
  732. if ( pUpdateDeleteKeys->GetString( "update/game/campaign" ) ||
  733. pUpdateDeleteKeys->GetString( "update/game/chapter" ) )
  734. {
  735. KeyValues *pAggregateSettings = pSettings->MakeCopy();
  736. KeyValues::AutoDelete autodelete( pAggregateSettings );
  737. pAggregateSettings->MergeFrom( pUpdateDeleteKeys );
  738. uint64 uiDlcMaskRequired = DetermineDlcRequiredMask( pAggregateSettings );
  739. if ( uiDlcMaskRequired != pAggregateSettings->GetUint64( "game/dlcrequired" ) )
  740. pUpdateDeleteKeys->SetUint64( "update/game/dlcrequired", uiDlcMaskRequired );
  741. }
  742. }
  743. // Prepares system for session creation
  744. KeyValues * CMatchTitleGameSettingsMgr::PrepareForSessionCreate( KeyValues *pSettings )
  745. {
  746. return MM_Title_RichPresence_PrepareForSessionCreate( pSettings );
  747. }
  748. // Prepares the lobby for game or adjust settings of new players who
  749. // join a game in progress, this function is allowed to modify
  750. // Members/Game subkeys and has to write modified players XUIDs
  751. void CMatchTitleGameSettingsMgr::PrepareLobbyForGame( KeyValues *pSettings, KeyValues **ppPlayersUpdated )
  752. {
  753. // set player avatar/teams, etc
  754. }
  755. // Prepares the host team lobby for game adjusting the game settings
  756. // this function is allowed to prepare modification package to update
  757. // Game subkeys.
  758. // Returns the update/delete package to be applied to session settings
  759. // and pushed to dependent two sesssion of the two teams.
  760. KeyValues * CMatchTitleGameSettingsMgr::PrepareTeamLinkForGame( KeyValues *pSettingsLocal, KeyValues *pSettingsRemote )
  761. {
  762. MEM_ALLOC_CREDIT();
  763. KeyValues *pUpdate = NULL;
  764. // Figure out game mode (game modes are assumed to match)
  765. char const *szGameMode = pSettingsLocal->GetString( "game/mode", "coop" );
  766. Assert( !Q_stricmp( szGameMode, pSettingsRemote->GetString( "game/mode", "coop" ) ) );
  767. // Check if either campaign is ANY
  768. char const *szCampaignLocal = pSettingsLocal->GetString( "game/campaign", "" );
  769. char const *szCampaignRemote = pSettingsRemote->GetString( "game/campaign", "" );
  770. // Campaigns should either designate ANY or should match
  771. Assert( !*szCampaignLocal || !*szCampaignRemote || !Q_stricmp( szCampaignLocal, szCampaignRemote ) );
  772. if ( !*szCampaignLocal || !*szCampaignRemote )
  773. {
  774. if ( !pUpdate )
  775. pUpdate = new KeyValues( "PrepareTeamLinkForGame" );
  776. // Assign a random campaign if none set
  777. #ifdef _DEMO
  778. const char *rnd = "L4D2C5";
  779. #else
  780. CFmtStr rnd( "L4D2C%d", RandomInt( 1, 5 ) );
  781. #endif
  782. char const *szCampaign = rnd;
  783. // If either session wants a specific campaign, honor them
  784. if ( *szCampaignLocal )
  785. szCampaign = szCampaignLocal;
  786. if ( *szCampaignRemote )
  787. szCampaign = szCampaignRemote;
  788. // Set the update key
  789. pUpdate->SetString( "update/game/campaign", szCampaign );
  790. szCampaignLocal = pUpdate->GetString( "update/game/campaign" );
  791. }
  792. // Check if the chapter is ANY
  793. int nChapterLocal = pSettingsLocal->GetInt( "game/chapter", 0 );
  794. int nChapterRemote = pSettingsRemote->GetInt( "game/chapter", 0 );
  795. // Chapters either designate ANY or should match
  796. Assert( !nChapterLocal || !nChapterRemote || nChapterRemote == nChapterLocal );
  797. if ( !nChapterLocal || !nChapterRemote )
  798. {
  799. if ( !pUpdate )
  800. pUpdate = new KeyValues( "PrepareTeamLinkForGame" );
  801. // Generate a random chapter if both are ANY
  802. int nChapter = 1;
  803. if ( !Q_stricmp( "teamscavenge", szGameMode ) )
  804. {
  805. int nChapters = g_pMatchExtSwarm->GetAllMissions()->GetInt( CFmtStr( "%s/modes/%s/chapters", szCampaignLocal, szGameMode ), 1 );
  806. nChapter = RandomInt( 1, nChapters );
  807. }
  808. // If any session wants a specific chapter, honor them
  809. if ( nChapterLocal )
  810. nChapter = nChapterLocal;
  811. if ( nChapterRemote )
  812. nChapter = nChapterRemote;
  813. // Set the update key
  814. pUpdate->SetInt( "update/game/chapter", nChapter );
  815. nChapterLocal = nChapter;
  816. }
  817. return pUpdate;
  818. }
  819. static void OnRunCommand_Avatar( KeyValues *pCommand, KeyValues *pSettings, KeyValues **ppPlayersUpdated )
  820. {
  821. /*
  822. MEM_ALLOC_CREDIT();
  823. XUID xuid = pCommand->GetUint64( "xuid" );
  824. char const *szAvatar = pCommand->GetString( "avatar", "" );
  825. KeyValues *pMembers = pSettings->FindKey( "members" );
  826. if ( !pMembers )
  827. return;
  828. // Find the avatar that is going to be updated
  829. KeyValues *pPlayer = SessionMembersFindPlayer( pSettings, xuid );
  830. if ( !pPlayer )
  831. return;
  832. // Check if the avatar is the same
  833. if ( !Q_stricmp( szAvatar, pPlayer->GetString( "game/avatar", "" ) ) )
  834. return;
  835. KeyValues *kvGame = pPlayer->FindKey( "game", true );
  836. if ( !kvGame )
  837. return;
  838. // If desired avatar is blank, then no validation required
  839. if ( !*szAvatar )
  840. {
  841. kvGame->SetString( "avatar", "" );
  842. }
  843. else
  844. {
  845. // Count how many times the avatar is currently in use
  846. int numCurrentlyUsed = 0;
  847. int numMachines = pMembers->GetInt( "numMachines" );
  848. for ( int k = 0; k < numMachines; ++ k )
  849. {
  850. KeyValues *pMachine = pMembers->FindKey( CFmtStr( "machine%d", k ) );
  851. if ( !pMachine )
  852. continue;
  853. int numPlayers = pMachine->GetInt( "numPlayers" );
  854. for ( int j = 0; j < numPlayers; ++ j )
  855. {
  856. char const *szUsedAvatar = pMachine->GetString( CFmtStr( "player%d/game/avatar", j ), "" );
  857. if ( !Q_stricmp( szUsedAvatar, szAvatar ) )
  858. ++ numCurrentlyUsed;
  859. }
  860. }
  861. // Validate that the avatar is not taken yet
  862. if ( !Q_stricmp( szAvatar, "Infected" ) )
  863. {
  864. if ( numCurrentlyUsed >= 4 )
  865. return;
  866. }
  867. else
  868. {
  869. if ( numCurrentlyUsed >= 1 )
  870. return;
  871. }
  872. kvGame->SetString( "avatar", szAvatar );
  873. }
  874. // Notify the sessions of a player update
  875. * ( ppPlayersUpdated ++ ) = pPlayer;
  876. */
  877. }
  878. // Executes the command on the session settings, this function on host
  879. // is allowed to modify Members/Game subkeys and has to fill in modified players KeyValues
  880. // When running on a remote client "ppPlayersUpdated" is NULL and players cannot
  881. // be modified
  882. void CMatchTitleGameSettingsMgr::ExecuteCommand( KeyValues *pCommand, KeyValues *pSessionSystemData, KeyValues *pSettings, KeyValues **ppPlayersUpdated )
  883. {
  884. char const *szCommand = pCommand->GetName();
  885. if ( !Q_stricmp( "Game::Avatar", szCommand ) )
  886. {
  887. if ( !Q_stricmp( "host", pSessionSystemData->GetString( "type", "host" ) ) &&
  888. // !Q_stricmp( "lobby", pSessionSystemData->GetString( "state", "lobby" ) ) && - avatars also update when players change team ingame
  889. ppPlayersUpdated )
  890. {
  891. char const *szAvatar = pCommand->GetString( "avatar" );
  892. if ( !*szAvatar )
  893. {
  894. // Requesting random is only allowed in unlocked lobby
  895. if ( Q_stricmp( "lobby", pSessionSystemData->GetString( "state", "lobby" ) ) )
  896. return;
  897. if ( *pSettings->GetString( "system/lock" ) )
  898. return;
  899. }
  900. OnRunCommand_Avatar( pCommand, pSettings, ppPlayersUpdated );
  901. return;
  902. }
  903. }
  904. }