Team Fortress 2 Source Code as on 22/4/2020
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.

531 lines
16 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Rich Presence support.
  4. // HACK: This file has also become the client wing of matchmaking. Matchmaking should
  5. // be re-factored to make a more complete client/server/engine interface
  6. //
  7. //=====================================================================================//
  8. #include "cbase.h"
  9. #include "tf_presence.h"
  10. #include "c_team_objectiveresource.h"
  11. #include "tf_gamerules.h"
  12. #include "c_tf_team.h"
  13. #include "c_tf_playerresource.h"
  14. #include "engine/imatchmaking.h"
  15. #include "ixboxsystem.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. // Global singleton
  19. static CTF_Presence s_presence;
  20. struct s_MapName
  21. {
  22. const char *pDiskName;
  23. const char *pDisplayName;
  24. };
  25. // This array must match the define order in hl2orange.spa.h
  26. static s_MapName s_Scenarios[] = {
  27. { "ctf_2fort", "2Fort" },
  28. { "cp_dustbowl", "Dustbowl" },
  29. { "cp_granary", "Granary" },
  30. { "cp_well", "Well" },
  31. { "cp_gravelpit", "Gravel Pit" },
  32. { "tc_hydro", "Hydro" },
  33. { "cloak", "Cloak (CTF)" },
  34. { "cp_cloak", "Cloak (CP)" },
  35. };
  36. struct s_PresenceTranslation
  37. {
  38. uint id;
  39. const char *pString;
  40. };
  41. // Only presence IDs can be searched by id number, because they're guaranteed to be unique
  42. static s_PresenceTranslation s_PresenceIds[] = {
  43. { CONTEXT_SCENARIO, "CONTEXT_SCENARIO" },
  44. { PROPERTY_CAPS_OWNED, "PROPERTY_CAPS_OWNED" },
  45. { PROPERTY_CAPS_TOTAL, "PROPERTY_CAPS_TOTAL" },
  46. { PROPERTY_FLAG_CAPTURE_LIMIT, "PROPERTY_FLAG_CAPTURE_LIMIT" },
  47. { PROPERTY_NUMBER_OF_ROUNDS, "PROPERTY_NUMBER_OF_ROUNDS" },
  48. { PROPERTY_WIN_LIMIT, "PROPERTY_WIN_LIMIT" },
  49. { PROPERTY_GAME_SIZE, "PROPERTY_GAME_SIZE" },
  50. { PROPERTY_AUTOBALANCE, "PROPERTY_AUTOBALANCE" },
  51. { PROPERTY_PRIVATE_SLOTS, "PROPERTY_PRIVATE_SLOTS" },
  52. { PROPERTY_MAX_GAME_TIME, "PROPERTY_MAX_GAME_TIME" },
  53. { PROPERTY_NUMBER_OF_TEAMS, "PROPERTY_NUMBER_OF_TEAMS" },
  54. { PROPERTY_TEAM, "PROPERTY_TEAM" },
  55. #if defined( _X360 )
  56. { X_CONTEXT_GAME_MODE, "CONTEXT_GAME_MODE" },
  57. { X_CONTEXT_GAME_TYPE, "CONTEXT_GAME_TYPE" },
  58. #endif
  59. };
  60. // Presence values cannot be searched by id number, because they are not unique
  61. static s_PresenceTranslation s_PresenceValues[] = {
  62. { SESSION_MATCH_QUERY_PLAYER_MATCH, "SESSION_MATCH_QUERY_PLAYER_MATCH" },
  63. { CONTEXT_GAME_MODE_MULTIPLAYER, "CONTEXT_GAME_MODE_MULTIPLAYER" },
  64. { CONTEXT_SCENARIO_CTF_2FORT, "CONTEXT_SCENARIO_CTF_2FORT" },
  65. { CONTEXT_SCENARIO_CP_DUSTBOWL, "CONTEXT_SCENARIO_CP_DUSTBOWL" },
  66. { CONTEXT_SCENARIO_CP_GRANARY, "CONTEXT_SCENARIO_CP_GRANARY" },
  67. { CONTEXT_SCENARIO_CP_WELL, "CONTEXT_SCENARIO_CP_WELL" },
  68. { CONTEXT_SCENARIO_CP_GRAVELPIT, "CONTEXT_SCENARIO_CP_GRAVELPIT" },
  69. { CONTEXT_SCENARIO_TC_HYDRO, "CONTEXT_SCENARIO_TC_HYDRO" },
  70. { CONTEXT_SCENARIO_CTF_CLOAK, "CONTEXT_SCENARIO_CTF_CLOAK" },
  71. { CONTEXT_SCENARIO_CP_CLOAK, "CONTEXT_SCENARIO_CP_CLOAK" },
  72. #if defined( _X360 )
  73. { XSESSION_CREATE_LIVE_MULTIPLAYER_STANDARD, "SESSION_CREATE_LIVE_MULTIPLAYER_STANDARD" },
  74. { XSESSION_CREATE_LIVE_MULTIPLAYER_RANKED, "SESSION_CREATE_LIVE_MULTIPLAYER_RANKED" },
  75. { XSESSION_CREATE_SYSTEMLINK, "SESSION_CREATE_SYSTEMLINK" },
  76. { X_CONTEXT_GAME_TYPE_STANDARD, "CONTEXT_GAME_TYPE_STANDARD" },
  77. { X_CONTEXT_GAME_TYPE_RANKED, "CONTEXT_GAME_TYPE_RANKED" },
  78. #endif
  79. };
  80. //-----------------------------------------------------------------------------
  81. // Convert a map name to a defined ID.
  82. //-----------------------------------------------------------------------------
  83. static unsigned int GetMapID( const char *pMapName )
  84. {
  85. for ( int i = 0; i < ARRAYSIZE( s_Scenarios ); ++i )
  86. {
  87. if ( !Q_stricmp( s_Scenarios[i].pDiskName, pMapName ) )
  88. {
  89. return i;
  90. }
  91. }
  92. return 0;
  93. }
  94. //-----------------------------------------------------------------------------
  95. // Convert a session property string to a display string for gameUI.
  96. //-----------------------------------------------------------------------------
  97. void CTF_Presence::GetPropertyDisplayString( uint id, uint value, char *pOutput, int nBytes )
  98. {
  99. const char *pDisplayString = "";
  100. switch( id )
  101. {
  102. #if defined( _X360 )
  103. case X_CONTEXT_GAME_TYPE:
  104. switch( value )
  105. {
  106. case X_CONTEXT_GAME_TYPE_STANDARD:
  107. pDisplayString = "#TF_Unranked";
  108. break;
  109. case X_CONTEXT_GAME_TYPE_RANKED:
  110. pDisplayString = "#TF_Ranked";
  111. break;
  112. }
  113. break;
  114. #endif
  115. case CONTEXT_SCENARIO:
  116. pDisplayString = s_Scenarios[value].pDisplayName;
  117. break;
  118. case PROPERTY_FLAG_CAPTURE_LIMIT:
  119. case PROPERTY_NUMBER_OF_ROUNDS:
  120. case PROPERTY_WIN_LIMIT:
  121. Q_snprintf( pOutput, nBytes, "%d", value );
  122. return;
  123. case PROPERTY_MAX_GAME_TIME:
  124. if ( value >= NO_TIME_LIMIT )
  125. {
  126. Q_strncpy( pOutput, "#TF_MaxTimeNoLimit", nBytes );
  127. }
  128. else
  129. {
  130. Q_snprintf( pOutput, nBytes, "%d:00", value );
  131. }
  132. return;
  133. case PROPERTY_TEAM:
  134. switch ( value )
  135. {
  136. case 0:
  137. pDisplayString = "blue";
  138. break;
  139. case 1:
  140. pDisplayString = "red";
  141. break;
  142. case 2:
  143. pDisplayString = "spectator";
  144. break;
  145. }
  146. break;
  147. default:
  148. pDisplayString = "Unknown";
  149. break;
  150. }
  151. Q_strncpy( pOutput, pDisplayString, nBytes );
  152. }
  153. //-----------------------------------------------------------------------------
  154. // Convert a presence ID to a string.
  155. //-----------------------------------------------------------------------------
  156. const char *CTF_Presence::GetPropertyIdString( const uint id )
  157. {
  158. for ( int i = 0; i < ARRAYSIZE( s_PresenceIds ); ++i )
  159. {
  160. if ( s_PresenceIds[i].id == id )
  161. {
  162. return s_PresenceIds[i].pString;
  163. }
  164. }
  165. return "Unknown";
  166. }
  167. //-----------------------------------------------------------------------------
  168. // Convert a session property string to an ID.
  169. //-----------------------------------------------------------------------------
  170. uint CTF_Presence::GetPresenceID( const char *pIDName )
  171. {
  172. for ( int i = 0; i < ARRAYSIZE( s_PresenceIds ); ++i )
  173. {
  174. if ( !Q_stricmp( s_PresenceIds[i].pString, pIDName ) )
  175. {
  176. return s_PresenceIds[i].id;
  177. }
  178. }
  179. for ( int i = 0; i < ARRAYSIZE( s_PresenceValues ); ++i )
  180. {
  181. if ( !Q_stricmp( s_PresenceValues[i].pString, pIDName ) )
  182. {
  183. return s_PresenceValues[i].id;
  184. }
  185. }
  186. Warning( "Presence ID not found for %s\n", pIDName );
  187. return 0;
  188. }
  189. //-----------------------------------------------------------------------------
  190. // Purpose: Level init
  191. //-----------------------------------------------------------------------------
  192. void CTF_Presence::LevelInitPreEntity( void )
  193. {
  194. m_bIsInCommentary = false;
  195. const char *pMapName = MapName();
  196. if ( pMapName )
  197. {
  198. UserSetContext( XBX_GetPrimaryUserId(), CONTEXT_SCENARIO, GetMapID( pMapName ), true );
  199. }
  200. }
  201. //-----------------------------------------------------------------------------
  202. // Purpose: Init
  203. //-----------------------------------------------------------------------------
  204. bool CTF_Presence::Init()
  205. {
  206. presence = &s_presence;
  207. ListenForGameEvent( "controlpoint_initialized" );
  208. ListenForGameEvent( "controlpoint_updateowner" );
  209. ListenForGameEvent( "teamplay_round_start" );
  210. ListenForGameEvent( "ctf_flag_captured" );
  211. ListenForGameEvent( "playing_commentary" );
  212. return CBasePresence::Init();
  213. }
  214. //-----------------------------------------------------------------------------
  215. // Get game session properties from matchmaking.
  216. //-----------------------------------------------------------------------------
  217. void CTF_Presence::SetupGameProperties( CUtlVector< XUSER_CONTEXT > &contexts, CUtlVector< XUSER_PROPERTY > &properties )
  218. {
  219. // Session properties have been set for this game. Use our knowledge of
  220. // the properties that have been defined for this game to set rules, cvars, etc.
  221. char buffer[MAX_PATH];
  222. #if 0 // defined( _X360 ) // absolutely nothing happens in this loop, so I disabled it. It was breaking the compiler in LTCG mode. -egr
  223. int count = contexts.Count();
  224. for ( int i = 0; i < count; ++i )
  225. {
  226. XUSER_CONTEXT &ctx = contexts[i];
  227. switch( ctx.dwContextId )
  228. {
  229. case X_CONTEXT_GAME_TYPE:
  230. if ( ctx.dwValue == X_CONTEXT_GAME_TYPE_RANKED )
  231. {
  232. }
  233. else if ( ctx.dwValue == X_CONTEXT_GAME_TYPE_STANDARD )
  234. {
  235. }
  236. break;
  237. }
  238. }
  239. #endif
  240. for ( int i = 0; i < properties.Count(); ++i )
  241. {
  242. XUSER_PROPERTY &prop = properties[i];
  243. switch( prop.dwPropertyId )
  244. {
  245. case PROPERTY_FLAG_CAPTURE_LIMIT:
  246. Q_snprintf( buffer, sizeof( buffer ), "tf_flag_caps_per_round %d", prop.value.nData );
  247. engine->ClientCmd( buffer );
  248. break;
  249. case PROPERTY_NUMBER_OF_ROUNDS:
  250. Q_snprintf( buffer, sizeof( buffer ), "mp_maxrounds %d", prop.value.nData );
  251. engine->ClientCmd( buffer );
  252. break;
  253. case PROPERTY_WIN_LIMIT:
  254. Q_snprintf( buffer, sizeof( buffer ), "mp_winlimit %d", prop.value.nData );
  255. engine->ClientCmd( buffer );
  256. break;
  257. case PROPERTY_GAME_SIZE:
  258. Q_snprintf( buffer, sizeof( buffer ), "maxplayers %d", prop.value.nData );
  259. engine->ClientCmd( buffer );
  260. break;
  261. case PROPERTY_AUTOBALANCE:
  262. Q_snprintf( buffer, sizeof( buffer ), "mp_autoteambalance %d", prop.value.nData );
  263. engine->ClientCmd( buffer );
  264. break;
  265. case PROPERTY_MAX_GAME_TIME:
  266. Q_snprintf( buffer, sizeof( buffer ), "mp_timelimit %d", prop.value.nData );
  267. engine->ClientCmd( buffer );
  268. break;
  269. }
  270. }
  271. }
  272. //-----------------------------------------------------------------------------
  273. // Respond to TF game events.
  274. //-----------------------------------------------------------------------------
  275. void CTF_Presence::FireGameEvent( IGameEvent *event )
  276. {
  277. const char *eventname = event->GetName();
  278. if ( !Q_stricmp( "teamplay_round_start", eventname ) )
  279. {
  280. // Set presence for this map
  281. // TODO: Set appropriate presence mode based on game type
  282. #if defined( _X360 )
  283. if ( TFGameRules() && !m_bIsInCommentary )
  284. {
  285. if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP )
  286. {
  287. UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_TF_CP, true );
  288. }
  289. else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CTF )
  290. {
  291. // ctf games start tied
  292. int zeroscore = 0;
  293. UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_PLAYER_TEAM_SCORE, sizeof(int), &zeroscore, true );
  294. UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_OPPONENT_TEAM_SCORE, sizeof(int), &zeroscore, true );
  295. UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_TF_CTF_TIED, true );
  296. }
  297. }
  298. #endif
  299. }
  300. else if ( !Q_stricmp( "controlpoint_initialized", eventname ) )
  301. {
  302. int nPoints = ObjectiveResource()->GetNumControlPoints();
  303. int nOwned = ObjectiveResource()->GetNumControlPointsOwned();
  304. UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_CAPS_TOTAL, sizeof(int), &nPoints, true );
  305. UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_CAPS_OWNED, sizeof(int), &nOwned, true );
  306. }
  307. else if ( !Q_stricmp( "controlpoint_updateowner", eventname ) )
  308. {
  309. int nOwned = ObjectiveResource()->GetNumControlPointsOwned();
  310. UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_CAPS_OWNED, sizeof(int), &nOwned, true );
  311. }
  312. else if ( !Q_stricmp( "ctf_flag_captured", eventname ) )
  313. {
  314. C_TFTeam *pLocalTeam = GetGlobalTFTeam( GetLocalPlayerTeam() );
  315. if ( pLocalTeam )
  316. {
  317. int iOtherScore = 0;
  318. int iTeamScore = 0;
  319. int iCappingTeam = event->GetInt( "capping_team" );
  320. int iCappingTeamScore = event->GetInt( "capping_team_score" );
  321. // If the local player is on the team that just captured
  322. if ( iCappingTeam == pLocalTeam->GetTeamNumber() )
  323. {
  324. // the newly capped score is our current score
  325. iTeamScore = iCappingTeamScore;
  326. }
  327. else // Other team capped
  328. {
  329. // Start other team score at the newly capped score set by the game event.
  330. // It can be higher than any we have locally recorded because of networking lag.
  331. iOtherScore = iCappingTeamScore;
  332. iTeamScore = pLocalTeam->GetFlagCaptures();
  333. }
  334. // highest score of any other team is the opposing score
  335. for ( int i = 0; i < g_Teams.Count(); i++ )
  336. {
  337. C_TFTeam* pCurTeam = ( dynamic_cast< C_TFTeam* >( g_Teams[i] ) );
  338. if ( pCurTeam )
  339. {
  340. if ( GetLocalPlayerTeam() == pCurTeam->GetTeamNumber() )
  341. continue;
  342. int iCurScore = pCurTeam->GetFlagCaptures();
  343. if ( iCurScore > iOtherScore )
  344. {
  345. iOtherScore = iCurScore;
  346. }
  347. }
  348. }
  349. UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_PLAYER_TEAM_SCORE, sizeof(int), &iTeamScore, true );
  350. UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_OPPONENT_TEAM_SCORE, sizeof(int), &iOtherScore, true );
  351. #if defined ( _X360 )
  352. if ( !m_bIsInCommentary )
  353. {
  354. if ( iTeamScore > iOtherScore )
  355. {
  356. UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_TF_CTF_WINNING, true );
  357. }
  358. else if ( iOtherScore > iTeamScore )
  359. {
  360. UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_TF_CTF_LOSING, true );
  361. }
  362. else
  363. {
  364. UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_TF_CTF_TIED, true );
  365. }
  366. }
  367. #endif
  368. }
  369. }
  370. else if ( !Q_stricmp( "playing_commentary", eventname ) )
  371. {
  372. m_bIsInCommentary = true;
  373. #if defined ( _X360 )
  374. UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_COMMENTARY, true );
  375. UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_GAME_MODE, CONTEXT_GAME_MODE_SINGLEPLAYER, true );
  376. #endif
  377. }
  378. }
  379. //-----------------------------------------------------------------------------
  380. // Purpose: Upload player stats to Live.
  381. //-----------------------------------------------------------------------------
  382. void CTF_Presence::UploadStats()
  383. {
  384. #if defined( _X360 )
  385. if ( m_bReportingStats )
  386. {
  387. m_ViewProperties[0].dwViewId = X_STATS_VIEW_SKILL;
  388. m_ViewProperties[1].dwViewId = m_bArbitrated ? STATS_VIEW_PLAYER_MAX_RANKED : STATS_VIEW_PLAYER_MAX_UNRANKED;
  389. m_ViewProperties[2].dwViewId = STATS_VIEW_PLAYER_MAX_UNRANKED;
  390. CUtlVector< XUSER_PROPERTY > skillStats;
  391. if ( !g_TF_PR )
  392. return;
  393. XUID localId = matchmaking->PlayerIdToXuid( GetLocalPlayerIndex() );
  394. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  395. {
  396. XUID id = matchmaking->PlayerIdToXuid( i );
  397. if ( id == 0 )
  398. continue;
  399. // For non-ranked sessions, only the local player's stats are written
  400. if ( !m_bArbitrated && id != localId )
  401. continue;
  402. skillStats.RemoveAll();
  403. int viewCt = 1;
  404. if ( id != 0 )
  405. {
  406. Msg( "XUID: %d\n", id );
  407. XUSER_PROPERTY prop;
  408. int nScore = g_TF_PR->GetTotalScore( i );
  409. // Write the player's skill stats
  410. prop.dwPropertyId = X_PROPERTY_RELATIVE_SCORE;
  411. prop.value.type = XUSER_DATA_TYPE_INT32;
  412. prop.value.nData = nScore;
  413. skillStats.AddToTail( prop );
  414. prop.dwPropertyId = X_PROPERTY_SESSION_TEAM;
  415. prop.value.type = XUSER_DATA_TYPE_INT32;
  416. prop.value.nData = i;
  417. skillStats.AddToTail( prop );
  418. m_ViewProperties[0].dwNumProperties = skillStats.Count();
  419. m_ViewProperties[0].pProperties = skillStats.Base();
  420. Msg( "Skill:\n" );
  421. Msg( "Relative Score: %d\n" , skillStats[0].value.nData );
  422. Msg( "Team: %d\n" , skillStats[1].value.nData );
  423. if ( id != localId )
  424. {
  425. // Write the remote player's points scored
  426. prop.dwPropertyId = PROPERTY_POINTS_SCORED;
  427. prop.value.type = XUSER_DATA_TYPE_INT64;
  428. prop.value.nData = nScore;
  429. m_ViewProperties[1].dwNumProperties = 1;
  430. m_ViewProperties[1].pProperties = &prop;
  431. viewCt = 2;
  432. Msg( "Points Scored: %d\n" , prop.value.nData );
  433. }
  434. else
  435. {
  436. // Write the local player's points scored
  437. prop.dwPropertyId = PROPERTY_POINTS_SCORED;
  438. prop.value.type = XUSER_DATA_TYPE_INT64;
  439. prop.value.nData = nScore;
  440. m_ViewProperties[1].dwNumProperties = 1;
  441. m_ViewProperties[1].pProperties = &prop;
  442. // Include the local player's array of personal stats
  443. m_ViewProperties[2].dwNumProperties = m_PlayerStats.Count();
  444. m_ViewProperties[2].pProperties = m_PlayerStats.Base();
  445. viewCt = 3;
  446. Msg( "Points Scored: %d\n" , prop.value.nData );
  447. Msg( "Unranked stat count: %d\n", m_ViewProperties[2].dwNumProperties );
  448. }
  449. }
  450. DWORD ret = xboxsystem->WriteStats( m_hSession, id , viewCt, m_ViewProperties, false );
  451. if ( ret != ERROR_SUCCESS )
  452. {
  453. Warning( "Write stats failed with error %d\n", ret );
  454. }
  455. }
  456. m_PlayerStats.RemoveAll();
  457. m_bReportingStats = false;
  458. }
  459. #endif
  460. }