diff options
Diffstat (limited to 'game/client/tf/tf_presence.cpp')
| -rw-r--r-- | game/client/tf/tf_presence.cpp | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/game/client/tf/tf_presence.cpp b/game/client/tf/tf_presence.cpp new file mode 100644 index 0000000..6c741f8 --- /dev/null +++ b/game/client/tf/tf_presence.cpp @@ -0,0 +1,531 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Rich Presence support. +// HACK: This file has also become the client wing of matchmaking. Matchmaking should +// be re-factored to make a more complete client/server/engine interface +// +//=====================================================================================// + +#include "cbase.h" +#include "tf_presence.h" +#include "c_team_objectiveresource.h" +#include "tf_gamerules.h" +#include "c_tf_team.h" +#include "c_tf_playerresource.h" +#include "engine/imatchmaking.h" +#include "ixboxsystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Global singleton +static CTF_Presence s_presence; + +struct s_MapName +{ + const char *pDiskName; + const char *pDisplayName; +}; + +// This array must match the define order in hl2orange.spa.h +static s_MapName s_Scenarios[] = { + { "ctf_2fort", "2Fort" }, + { "cp_dustbowl", "Dustbowl" }, + { "cp_granary", "Granary" }, + { "cp_well", "Well" }, + { "cp_gravelpit", "Gravel Pit" }, + { "tc_hydro", "Hydro" }, + { "cloak", "Cloak (CTF)" }, + { "cp_cloak", "Cloak (CP)" }, +}; + +struct s_PresenceTranslation +{ + uint id; + const char *pString; +}; + +// Only presence IDs can be searched by id number, because they're guaranteed to be unique +static s_PresenceTranslation s_PresenceIds[] = { + { CONTEXT_SCENARIO, "CONTEXT_SCENARIO" }, + { PROPERTY_CAPS_OWNED, "PROPERTY_CAPS_OWNED" }, + { PROPERTY_CAPS_TOTAL, "PROPERTY_CAPS_TOTAL" }, + { PROPERTY_FLAG_CAPTURE_LIMIT, "PROPERTY_FLAG_CAPTURE_LIMIT" }, + { PROPERTY_NUMBER_OF_ROUNDS, "PROPERTY_NUMBER_OF_ROUNDS" }, + { PROPERTY_WIN_LIMIT, "PROPERTY_WIN_LIMIT" }, + { PROPERTY_GAME_SIZE, "PROPERTY_GAME_SIZE" }, + { PROPERTY_AUTOBALANCE, "PROPERTY_AUTOBALANCE" }, + { PROPERTY_PRIVATE_SLOTS, "PROPERTY_PRIVATE_SLOTS" }, + { PROPERTY_MAX_GAME_TIME, "PROPERTY_MAX_GAME_TIME" }, + { PROPERTY_NUMBER_OF_TEAMS, "PROPERTY_NUMBER_OF_TEAMS" }, + { PROPERTY_TEAM, "PROPERTY_TEAM" }, +#if defined( _X360 ) + { X_CONTEXT_GAME_MODE, "CONTEXT_GAME_MODE" }, + { X_CONTEXT_GAME_TYPE, "CONTEXT_GAME_TYPE" }, +#endif +}; + +// Presence values cannot be searched by id number, because they are not unique +static s_PresenceTranslation s_PresenceValues[] = { + { SESSION_MATCH_QUERY_PLAYER_MATCH, "SESSION_MATCH_QUERY_PLAYER_MATCH" }, + { CONTEXT_GAME_MODE_MULTIPLAYER, "CONTEXT_GAME_MODE_MULTIPLAYER" }, + { CONTEXT_SCENARIO_CTF_2FORT, "CONTEXT_SCENARIO_CTF_2FORT" }, + { CONTEXT_SCENARIO_CP_DUSTBOWL, "CONTEXT_SCENARIO_CP_DUSTBOWL" }, + { CONTEXT_SCENARIO_CP_GRANARY, "CONTEXT_SCENARIO_CP_GRANARY" }, + { CONTEXT_SCENARIO_CP_WELL, "CONTEXT_SCENARIO_CP_WELL" }, + { CONTEXT_SCENARIO_CP_GRAVELPIT, "CONTEXT_SCENARIO_CP_GRAVELPIT" }, + { CONTEXT_SCENARIO_TC_HYDRO, "CONTEXT_SCENARIO_TC_HYDRO" }, + { CONTEXT_SCENARIO_CTF_CLOAK, "CONTEXT_SCENARIO_CTF_CLOAK" }, + { CONTEXT_SCENARIO_CP_CLOAK, "CONTEXT_SCENARIO_CP_CLOAK" }, +#if defined( _X360 ) + { XSESSION_CREATE_LIVE_MULTIPLAYER_STANDARD, "SESSION_CREATE_LIVE_MULTIPLAYER_STANDARD" }, + { XSESSION_CREATE_LIVE_MULTIPLAYER_RANKED, "SESSION_CREATE_LIVE_MULTIPLAYER_RANKED" }, + { XSESSION_CREATE_SYSTEMLINK, "SESSION_CREATE_SYSTEMLINK" }, + { X_CONTEXT_GAME_TYPE_STANDARD, "CONTEXT_GAME_TYPE_STANDARD" }, + { X_CONTEXT_GAME_TYPE_RANKED, "CONTEXT_GAME_TYPE_RANKED" }, +#endif +}; + +//----------------------------------------------------------------------------- +// Convert a map name to a defined ID. +//----------------------------------------------------------------------------- +static unsigned int GetMapID( const char *pMapName ) +{ + for ( int i = 0; i < ARRAYSIZE( s_Scenarios ); ++i ) + { + if ( !Q_stricmp( s_Scenarios[i].pDiskName, pMapName ) ) + { + return i; + } + } + return 0; +} + +//----------------------------------------------------------------------------- +// Convert a session property string to a display string for gameUI. +//----------------------------------------------------------------------------- +void CTF_Presence::GetPropertyDisplayString( uint id, uint value, char *pOutput, int nBytes ) +{ + const char *pDisplayString = ""; + + switch( id ) + { +#if defined( _X360 ) + case X_CONTEXT_GAME_TYPE: + switch( value ) + { + case X_CONTEXT_GAME_TYPE_STANDARD: + pDisplayString = "#TF_Unranked"; + break; + + case X_CONTEXT_GAME_TYPE_RANKED: + pDisplayString = "#TF_Ranked"; + break; + } + break; +#endif + case CONTEXT_SCENARIO: + pDisplayString = s_Scenarios[value].pDisplayName; + break; + + case PROPERTY_FLAG_CAPTURE_LIMIT: + case PROPERTY_NUMBER_OF_ROUNDS: + case PROPERTY_WIN_LIMIT: + Q_snprintf( pOutput, nBytes, "%d", value ); + return; + + case PROPERTY_MAX_GAME_TIME: + if ( value >= NO_TIME_LIMIT ) + { + Q_strncpy( pOutput, "#TF_MaxTimeNoLimit", nBytes ); + } + else + { + Q_snprintf( pOutput, nBytes, "%d:00", value ); + } + return; + + case PROPERTY_TEAM: + switch ( value ) + { + case 0: + pDisplayString = "blue"; + break; + + case 1: + pDisplayString = "red"; + break; + + case 2: + pDisplayString = "spectator"; + break; + } + break; + + default: + pDisplayString = "Unknown"; + break; + } + + Q_strncpy( pOutput, pDisplayString, nBytes ); +} + +//----------------------------------------------------------------------------- +// Convert a presence ID to a string. +//----------------------------------------------------------------------------- +const char *CTF_Presence::GetPropertyIdString( const uint id ) +{ + for ( int i = 0; i < ARRAYSIZE( s_PresenceIds ); ++i ) + { + if ( s_PresenceIds[i].id == id ) + { + return s_PresenceIds[i].pString; + } + } + return "Unknown"; +} + +//----------------------------------------------------------------------------- +// Convert a session property string to an ID. +//----------------------------------------------------------------------------- +uint CTF_Presence::GetPresenceID( const char *pIDName ) +{ + for ( int i = 0; i < ARRAYSIZE( s_PresenceIds ); ++i ) + { + if ( !Q_stricmp( s_PresenceIds[i].pString, pIDName ) ) + { + return s_PresenceIds[i].id; + } + } + + for ( int i = 0; i < ARRAYSIZE( s_PresenceValues ); ++i ) + { + if ( !Q_stricmp( s_PresenceValues[i].pString, pIDName ) ) + { + return s_PresenceValues[i].id; + } + } + + Warning( "Presence ID not found for %s\n", pIDName ); + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Level init +//----------------------------------------------------------------------------- +void CTF_Presence::LevelInitPreEntity( void ) +{ + m_bIsInCommentary = false; + const char *pMapName = MapName(); + if ( pMapName ) + { + UserSetContext( XBX_GetPrimaryUserId(), CONTEXT_SCENARIO, GetMapID( pMapName ), true ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Init +//----------------------------------------------------------------------------- +bool CTF_Presence::Init() +{ + presence = &s_presence; + + ListenForGameEvent( "controlpoint_initialized" ); + ListenForGameEvent( "controlpoint_updateowner" ); + ListenForGameEvent( "teamplay_round_start" ); + ListenForGameEvent( "ctf_flag_captured" ); + ListenForGameEvent( "playing_commentary" ); + + return CBasePresence::Init(); +} + +//----------------------------------------------------------------------------- +// Get game session properties from matchmaking. +//----------------------------------------------------------------------------- +void CTF_Presence::SetupGameProperties( CUtlVector< XUSER_CONTEXT > &contexts, CUtlVector< XUSER_PROPERTY > &properties ) +{ + // Session properties have been set for this game. Use our knowledge of + // the properties that have been defined for this game to set rules, cvars, etc. + char buffer[MAX_PATH]; + +#if 0 // defined( _X360 ) // absolutely nothing happens in this loop, so I disabled it. It was breaking the compiler in LTCG mode. -egr + int count = contexts.Count(); + for ( int i = 0; i < count; ++i ) + { + XUSER_CONTEXT &ctx = contexts[i]; + switch( ctx.dwContextId ) + { + case X_CONTEXT_GAME_TYPE: + if ( ctx.dwValue == X_CONTEXT_GAME_TYPE_RANKED ) + { + } + else if ( ctx.dwValue == X_CONTEXT_GAME_TYPE_STANDARD ) + { + } + break; + } + } +#endif + + for ( int i = 0; i < properties.Count(); ++i ) + { + XUSER_PROPERTY &prop = properties[i]; + switch( prop.dwPropertyId ) + { + case PROPERTY_FLAG_CAPTURE_LIMIT: + Q_snprintf( buffer, sizeof( buffer ), "tf_flag_caps_per_round %d", prop.value.nData ); + engine->ClientCmd( buffer ); + break; + + case PROPERTY_NUMBER_OF_ROUNDS: + Q_snprintf( buffer, sizeof( buffer ), "mp_maxrounds %d", prop.value.nData ); + engine->ClientCmd( buffer ); + break; + + case PROPERTY_WIN_LIMIT: + Q_snprintf( buffer, sizeof( buffer ), "mp_winlimit %d", prop.value.nData ); + engine->ClientCmd( buffer ); + break; + + case PROPERTY_GAME_SIZE: + Q_snprintf( buffer, sizeof( buffer ), "maxplayers %d", prop.value.nData ); + engine->ClientCmd( buffer ); + break; + + case PROPERTY_AUTOBALANCE: + Q_snprintf( buffer, sizeof( buffer ), "mp_autoteambalance %d", prop.value.nData ); + engine->ClientCmd( buffer ); + break; + + case PROPERTY_MAX_GAME_TIME: + Q_snprintf( buffer, sizeof( buffer ), "mp_timelimit %d", prop.value.nData ); + engine->ClientCmd( buffer ); + break; + + } + } +} + + +//----------------------------------------------------------------------------- +// Respond to TF game events. +//----------------------------------------------------------------------------- +void CTF_Presence::FireGameEvent( IGameEvent *event ) +{ + const char *eventname = event->GetName(); + + if ( !Q_stricmp( "teamplay_round_start", eventname ) ) + { + // Set presence for this map + // TODO: Set appropriate presence mode based on game type +#if defined( _X360 ) + if ( TFGameRules() && !m_bIsInCommentary ) + { + if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP ) + { + UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_TF_CP, true ); + } + else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CTF ) + { + // ctf games start tied + int zeroscore = 0; + UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_PLAYER_TEAM_SCORE, sizeof(int), &zeroscore, true ); + UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_OPPONENT_TEAM_SCORE, sizeof(int), &zeroscore, true ); + UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_TF_CTF_TIED, true ); + } + } +#endif + } + else if ( !Q_stricmp( "controlpoint_initialized", eventname ) ) + { + int nPoints = ObjectiveResource()->GetNumControlPoints(); + int nOwned = ObjectiveResource()->GetNumControlPointsOwned(); + + UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_CAPS_TOTAL, sizeof(int), &nPoints, true ); + UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_CAPS_OWNED, sizeof(int), &nOwned, true ); + } + else if ( !Q_stricmp( "controlpoint_updateowner", eventname ) ) + { + int nOwned = ObjectiveResource()->GetNumControlPointsOwned(); + UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_CAPS_OWNED, sizeof(int), &nOwned, true ); + } + else if ( !Q_stricmp( "ctf_flag_captured", eventname ) ) + { + C_TFTeam *pLocalTeam = GetGlobalTFTeam( GetLocalPlayerTeam() ); + + if ( pLocalTeam ) + { + int iOtherScore = 0; + int iTeamScore = 0; + int iCappingTeam = event->GetInt( "capping_team" ); + int iCappingTeamScore = event->GetInt( "capping_team_score" ); + + // If the local player is on the team that just captured + if ( iCappingTeam == pLocalTeam->GetTeamNumber() ) + { + // the newly capped score is our current score + iTeamScore = iCappingTeamScore; + } + else // Other team capped + { + // Start other team score at the newly capped score set by the game event. + // It can be higher than any we have locally recorded because of networking lag. + iOtherScore = iCappingTeamScore; + iTeamScore = pLocalTeam->GetFlagCaptures(); + } + + // highest score of any other team is the opposing score + for ( int i = 0; i < g_Teams.Count(); i++ ) + { + C_TFTeam* pCurTeam = ( dynamic_cast< C_TFTeam* >( g_Teams[i] ) ); + if ( pCurTeam ) + { + if ( GetLocalPlayerTeam() == pCurTeam->GetTeamNumber() ) + continue; + + int iCurScore = pCurTeam->GetFlagCaptures(); + + if ( iCurScore > iOtherScore ) + { + iOtherScore = iCurScore; + } + } + } + + UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_PLAYER_TEAM_SCORE, sizeof(int), &iTeamScore, true ); + UserSetProperty( XBX_GetPrimaryUserId(), PROPERTY_OPPONENT_TEAM_SCORE, sizeof(int), &iOtherScore, true ); +#if defined ( _X360 ) + if ( !m_bIsInCommentary ) + { + if ( iTeamScore > iOtherScore ) + { + UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_TF_CTF_WINNING, true ); + } + else if ( iOtherScore > iTeamScore ) + { + UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_TF_CTF_LOSING, true ); + } + else + { + UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_TF_CTF_TIED, true ); + } + } +#endif + } + } + else if ( !Q_stricmp( "playing_commentary", eventname ) ) + { + m_bIsInCommentary = true; +#if defined ( _X360 ) + UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_COMMENTARY, true ); + UserSetContext( XBX_GetPrimaryUserId(), X_CONTEXT_GAME_MODE, CONTEXT_GAME_MODE_SINGLEPLAYER, true ); +#endif + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Upload player stats to Live. +//----------------------------------------------------------------------------- +void CTF_Presence::UploadStats() +{ +#if defined( _X360 ) + if ( m_bReportingStats ) + { + m_ViewProperties[0].dwViewId = X_STATS_VIEW_SKILL; + m_ViewProperties[1].dwViewId = m_bArbitrated ? STATS_VIEW_PLAYER_MAX_RANKED : STATS_VIEW_PLAYER_MAX_UNRANKED; + m_ViewProperties[2].dwViewId = STATS_VIEW_PLAYER_MAX_UNRANKED; + + CUtlVector< XUSER_PROPERTY > skillStats; + + if ( !g_TF_PR ) + return; + + XUID localId = matchmaking->PlayerIdToXuid( GetLocalPlayerIndex() ); + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + XUID id = matchmaking->PlayerIdToXuid( i ); + if ( id == 0 ) + continue; + + // For non-ranked sessions, only the local player's stats are written + if ( !m_bArbitrated && id != localId ) + continue; + + skillStats.RemoveAll(); + int viewCt = 1; + + if ( id != 0 ) + { + Msg( "XUID: %d\n", id ); + XUSER_PROPERTY prop; + + int nScore = g_TF_PR->GetTotalScore( i ); + + // Write the player's skill stats + prop.dwPropertyId = X_PROPERTY_RELATIVE_SCORE; + prop.value.type = XUSER_DATA_TYPE_INT32; + prop.value.nData = nScore; + skillStats.AddToTail( prop ); + + prop.dwPropertyId = X_PROPERTY_SESSION_TEAM; + prop.value.type = XUSER_DATA_TYPE_INT32; + prop.value.nData = i; + skillStats.AddToTail( prop ); + + m_ViewProperties[0].dwNumProperties = skillStats.Count(); + m_ViewProperties[0].pProperties = skillStats.Base(); + + Msg( "Skill:\n" ); + Msg( "Relative Score: %d\n" , skillStats[0].value.nData ); + Msg( "Team: %d\n" , skillStats[1].value.nData ); + + if ( id != localId ) + { + // Write the remote player's points scored + prop.dwPropertyId = PROPERTY_POINTS_SCORED; + prop.value.type = XUSER_DATA_TYPE_INT64; + prop.value.nData = nScore; + + m_ViewProperties[1].dwNumProperties = 1; + m_ViewProperties[1].pProperties = ∝ + + viewCt = 2; + + Msg( "Points Scored: %d\n" , prop.value.nData ); + } + else + { + // Write the local player's points scored + prop.dwPropertyId = PROPERTY_POINTS_SCORED; + prop.value.type = XUSER_DATA_TYPE_INT64; + prop.value.nData = nScore; + m_ViewProperties[1].dwNumProperties = 1; + m_ViewProperties[1].pProperties = ∝ + + // Include the local player's array of personal stats + m_ViewProperties[2].dwNumProperties = m_PlayerStats.Count(); + m_ViewProperties[2].pProperties = m_PlayerStats.Base(); + + viewCt = 3; + + Msg( "Points Scored: %d\n" , prop.value.nData ); + Msg( "Unranked stat count: %d\n", m_ViewProperties[2].dwNumProperties ); + } + } + + DWORD ret = xboxsystem->WriteStats( m_hSession, id , viewCt, m_ViewProperties, false ); + if ( ret != ERROR_SUCCESS ) + { + Warning( "Write stats failed with error %d\n", ret ); + } + } + + m_PlayerStats.RemoveAll(); + m_bReportingStats = false; + } +#endif +} + |