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
531 lines
16 KiB
//========= 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 |
|
} |
|
|
|
|