|
|
//===== Copyright � 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "mm_title_richpresence.h"
#include "matchmaking/cstrike15/imatchext_cstrike15.h"
#include "vstdlib/random.h"
#include "fmtstr.h"
#include "../engine/filesystem_engine.h"
#include "filesystem.h"
#include "gametypes/igametypes.h"
#include "mathlib/expressioncalculator.h"
#include "csgo.spa.h"
#include "mm_title_contextvalues.h"
#include "inputsystem/iinputsystem.h"
#if !defined (NO_STEAM)
#include "steam/steam_api.h"
extern CSteamAPIContext *steamapicontext; #endif
#include "csgo_limits.h"
#include "csgo_limits.inl"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern IGameTypes *g_pGameTypes;
struct AggregateSkillProperty { char const *szSkill; unsigned int dwSkillPropertyId; unsigned int dwSkillMinPropertyId; unsigned int dwSkillMaxPropertyId; };
static AggregateSkillProperty g_AggregateSkillProperties[] = { { "skill0", PROPERTY_CSS_AGGREGATE_SKILL0, PROPERTY_CSS_SEARCH_SKILL0_MIN, PROPERTY_CSS_SEARCH_SKILL0_MAX }, { "skill1", PROPERTY_CSS_AGGREGATE_SKILL1, PROPERTY_CSS_SEARCH_SKILL1_MIN, PROPERTY_CSS_SEARCH_SKILL1_MAX }, { "skill2", PROPERTY_CSS_AGGREGATE_SKILL2, PROPERTY_CSS_SEARCH_SKILL2_MIN, PROPERTY_CSS_SEARCH_SKILL2_MAX }, { "skill3", PROPERTY_CSS_AGGREGATE_SKILL3, PROPERTY_CSS_SEARCH_SKILL3_MIN, PROPERTY_CSS_SEARCH_SKILL3_MAX }, { "skill4", PROPERTY_CSS_AGGREGATE_SKILL4, PROPERTY_CSS_SEARCH_SKILL4_MIN, PROPERTY_CSS_SEARCH_SKILL4_MAX }, NULL, };
#define MATCH_MAX_SKILL_FIELDS 5
ConVar mm_sv_load_test( "mm_sv_load_test", "0", FCVAR_DEVELOPMENTONLY ); ConVar mm_title_debug_version( "mm_title_debug_version", "0", FCVAR_DEVELOPMENTONLY, "This matchmaking version will override .res file version for isolating matchmaking" ); ConVar mm_title_debug_dccheck( "mm_title_debug_dccheck", "0", FCVAR_DEVELOPMENTONLY, "This matchmaking query will override datacenter connectivity: -1 for local, 1 for dedicated" ); ConVar mm_title_debug_minquery( "mm_title_debug_minquery", "0", FCVAR_DEVELOPMENTONLY, "This matchmaking query will run with minimal set of parameters" ); ConVar mm_csgo_community_search_players_min( "mm_csgo_community_search_players_min", "3", FCVAR_RELEASE | FCVAR_ARCHIVE, "When performing CSGO community matchmaking look for servers with at least so many human players" );
class CMatchTitleGameSettingsMgr : public IMatchTitleGameSettingsMgr { public:
CMatchTitleGameSettingsMgr() { m_pMatchSystemData = NULL; }
~CMatchTitleGameSettingsMgr() { if ( m_pMatchSystemData ) { m_pMatchSystemData->deleteThis(); } }
// Extends server game details
virtual void ExtendServerDetails( KeyValues *pDetails, KeyValues *pRequest );
// Adds the essential part of game details to be broadcast
virtual void ExtendLobbyDetailsTemplate( KeyValues *pDetails, char const *szReason, KeyValues *pFullSettings );
// Extends game settings update packet for lobby transition,
// either due to a migration or due to an endgame condition
virtual void ExtendGameSettingsForLobbyTransition( KeyValues *pSettings, KeyValues *pSettingsUpdate, bool bEndGame );
// Allows title to migrate data cached in client sys session data
// into host sys session data
virtual void MigrateSysSessionData( IMatchSession *pNewMatchSession, KeyValues *pSysSessionData );
// Adds data for datacenter reporting
virtual void ExtendDatacenterReport( KeyValues *pReportMsg, char const *szReason );
// Rolls up game details for matches grouping
virtual KeyValues * RollupGameDetails( KeyValues *pDetails, KeyValues *pRollup, KeyValues *pQuery );
// Defines session search keys for matchmaking
virtual KeyValues * DefineSessionSearchKeys( KeyValues *pSettings );
// Defines dedicated server search key
virtual KeyValues * DefineDedicatedSearchKeys( KeyValues *pSettings, bool bNeedOfficialServer, int nSearchPass );
// Extends game settings update packet before it gets merged with
// session settings and networked to remote clients
virtual void ExtendGameSettingsUpdateKeys( KeyValues *pSettings, KeyValues *pUpdateDeleteKeys );
// Update a team session to be a game session by filling in map name, updating number of slots etc
virtual KeyValues * ExtendTeamLobbyToGame( KeyValues *pSettings );
// Prepares system for session creation
virtual KeyValues * PrepareForSessionCreate( KeyValues *pSettings );
// Executes the command on the session settings, this function on host
// is allowed to modify Members/Game subkeys and has to fill in modified players KeyValues
// When running on a remote client "ppPlayersUpdated" is NULL and players cannot
// be modified
virtual void ExecuteCommand( KeyValues *pCommand, KeyValues *pSessionSystemData, KeyValues *pSettings, KeyValues **ppPlayersUpdated );
// Prepares the lobby for game or adjust settings of new players who
// join a game in progress, this function is allowed to modify
// Members/Game subkeys and has to fill in modified players KeyValues
virtual void PrepareLobbyForGame( KeyValues *pSettings, KeyValues **ppPlayersUpdated );
// Prepares the host team lobby for game adjusting the game settings
// this function is allowed to prepare modification package to update
// Game subkeys.
// Returns the update/delete package to be applied to session settings
// and pushed to dependent two sesssion of the two teams.
virtual KeyValues * PrepareTeamLinkForGame( KeyValues *pSettingsLocal, KeyValues *pSettingsRemote );
// Initializes full game settings from potentially abbreviated game settings
virtual void InitializeGameSettings( KeyValues *pSettings, char const *szReason );
// Sets the bspname key given a mapgroup
virtual void SetBspnameFromMapgroup( KeyValues *pSettings );
// Prepares the client lobby for migration
// this function is called when the client session is still in the state
// of "client" while handling the original host disconnection and decision
// has been made that local machine will be elected as new "host"
// Returns NULL if migration should proceed normally
// Returns [ kvroot { "error" "n/a" } ] if migration should be aborted.
virtual KeyValues * PrepareClientLobbyForMigration( KeyValues *pSettingsLocal, KeyValues *pMigrationInfo ) { return NULL; }
// Prepares the session for server disconnect
// this function is called when the session is still in the active gameplay
// state and while localhost is handling the disconnection from game server.
// Returns NULL to allow default flow
// Returns [ kvroot { "disconnecthdlr" "<opt>" } ] where <opt> can be:
// "destroy" : to trigger a disconnection error and destroy the session
// "lobby" : to initiate a "salvaging" lobby transition
virtual KeyValues * PrepareClientLobbyForGameDisconnect( KeyValues *pSettingsLocal, KeyValues *pDisconnectInfo ) { // Every event that causes a disconnection from game server is unsalvagable in CS:GO
// in other products it should be possible to keep all players that were playing on game server
// in the lobby together and send them to lobby UI
return new KeyValues( "disconnecthdrl", "disconnecthdlr", "destroy" ); }
// Retrieves the indexed formula from the match system settings file. (MatchSystem.360.res)
virtual char const * GetFormulaAverage( int index );
// Called by the client to notify matchmaking that it should update matchmaking properties based
// on player distribution among the teams.
virtual void UpdateTeamProperties( KeyValues *pCurrentSettings, KeyValues *pTeamProperties );
// Validates if client profile can set a stat or get awarded an achievement
virtual bool AllowClientProfileUpdate( KeyValues *kvUpdate ) { return true; // Always all profile to be updated for CStrike15, all platforms
}
protected: // Loads the games match settings from the KeyValues object. The match settings contain
// data on how to expand the search passes formulas to use for computing skill.
void LoadMatchSettings( void );
// Add filters necessary to implement matchmaking rule on Steam
void AddSteamMatchmakingRule( KeyValues *pResult, bool bAllSessions, KeyValues *pSettings, bool bCssMatchVersion, bool bCssLevel, bool bCssGameType, bool bCssGameMode, bool bTeamMatch ); KeyValues *m_pMatchSystemData;
CUtlVector< CUtlString > m_FormulaAverage;
CUtlVector< CUtlString > m_FormulaExperience; int m_nFormulaExperienceRangeMin; int m_nFormulaExperienceRangeMax;
struct SkillFormulas { CUtlVector< CUtlString > formulas; int rangeMin; int rangeMax; }; CUtlVector< SkillFormulas* > m_FormulaSkill;
struct SearchPass { bool checkExperience; int experienceRange; CUtlVector< bool > checkSkill; CUtlVector< int > skillRange; }; CUtlVector< SearchPass* > m_SearchPass; };
CMatchTitleGameSettingsMgr g_MatchTitleGameSettingsMgr; IMatchTitleGameSettingsMgr *g_pIMatchTitleGameSettingsMgr = &g_MatchTitleGameSettingsMgr;
//
// Implementation of CMatchTitleGameSettingsMgr
//
// Extends server game details
void CMatchTitleGameSettingsMgr::ExtendServerDetails( KeyValues *pDetails, KeyValues *pRequest ) { // Query server info
INetSupport::ServerInfo_t si; g_pMatchExtensions->GetINetSupport()->GetServerInfo( &si );
// Server is always in game
pDetails->SetString( "game/state", "game" );
//
// Determine map info
//
{ int mode = g_pGameTypes->GetCurrentGameMode(); int type = g_pGameTypes->GetCurrentGameType(); const char *modeName = g_pGameTypes->GetGameModeFromInt( type, mode ); const char *typeName = g_pGameTypes->GetGameTypeFromInt( type );
int numHumanSlots = g_pGameTypes->GetMaxPlayersForTypeAndMode( type, mode ); pDetails->SetInt( "members/numSlots", numHumanSlots );
pDetails->SetString( "game/map", si.m_szMapName ); pDetails->SetString( "game/mapgroupname", si.m_szMapGroupName ); pDetails->SetString( "game/mode", modeName ); pDetails->SetString( "game/type", typeName ); if ( !g_pMatchExtensions->GetIServerGameDLL()->IsValveDS() ) { pDetails->SetInt( "game/hosted", 1 ); pDetails->SetString( "options/server", "dedicated" ); } } }
// Adds the essential part of game details to be broadcast
void CMatchTitleGameSettingsMgr::ExtendLobbyDetailsTemplate( KeyValues *pDetails, char const *szReason, KeyValues *pFullSettings ) { static KeyValues *pkvExt = KeyValues::FromString( "settings", " game { " " mapgroupname #empty# " " map #empty# " " mode #empty# " " type #empty# " " state #empty# " " hosted 0 " " spectate 0 " " apr 0 " " ark 0 " " loc #empty# " " clanid #empty# " " clantag #empty# " " } " );
// TODO: Is this the appropriate spot to add in game values to initialize the dedicated server state?
pDetails->MergeFrom( pkvExt, KeyValues::MERGE_KV_UPDATE ); }
// Extends game settings update packet for lobby transition,
// either due to a migration or due to an endgame condition
void CMatchTitleGameSettingsMgr::ExtendGameSettingsForLobbyTransition( KeyValues *pSettings, KeyValues *pSettingsUpdate, bool bEndGame ) { pSettingsUpdate->SetString( "game/state", "lobby" );
extern void UpdateAggregateMembersSettings( KeyValues *pFullGameSettings, KeyValues *pUpdate ); UpdateAggregateMembersSettings( pSettings, pSettingsUpdate ); }
// Allows title to migrate data cached in client sys session data
// into host sys session data
void CMatchTitleGameSettingsMgr::MigrateSysSessionData( IMatchSession *pNewMatchSession, KeyValues *pSysSessionData ) { #ifdef _PS3
KeyValues *kvSystemData = pNewMatchSession->GetSessionSystemData(); if ( !kvSystemData ) return;
if ( KeyValues *kvTimeout = pSysSessionData->FindKey( "timeout", false ) ) { int avgRank = kvTimeout->GetInt(); kvSystemData->SetInt( "timeout", avgRank );
DevMsg( "Session timeout value=%d (%s)\n", avgRank, "migrated" ); steamapicontext->SteamMatchmaking()->SetLobbyData( kvSystemData->GetUint64( "xuidReserve", 0ull ), "game:timeout", CFmtStr( "%u", avgRank ) ); }
if ( KeyValues *kvNumOpenSlots = pSysSessionData->FindKey( "numOpenSlots", false ) ) { int numOpenSlots = kvNumOpenSlots->GetInt(); kvSystemData->SetInt( "numOpenSlots", numOpenSlots );
DevMsg( "Session numOpenSlots=%d (%s)\n", numOpenSlots, "migrated" ); steamapicontext->SteamMatchmaking()->SetLobbyData( kvSystemData->GetUint64( "xuidReserve", 0ull ), "game:numOpenSlots", CFmtStr( "%u", numOpenSlots ) ); } #endif
}
// Adds data for datacenter reporting
void CMatchTitleGameSettingsMgr::ExtendDatacenterReport( KeyValues *cmd, char const *szReason ) { #ifdef _X360
//if ( XBX_GetPrimaryUserId() == XBX_INVALID_USER_ID )
// return;
//if ( !XBX_GetNumGameUsers() || XBX_GetPrimaryUserIsGuest() )
// return;
//IPlayerLocal *pLocalPlayer = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() );
//if ( !pLocalPlayer )
// return;
//// Achievements info
//uint64 uiAchMask1 = 0; // 100 bits for achievements
//uint64 uiAchMask2 = 0; // 27 bits for asset awards
//{
// KeyValues *kvAchInfo = new KeyValues( "", "@achievements", 0, "@awards", 0 );
// KeyValues::AutoDelete autodelete_kvAchInfo( kvAchInfo );
// pLocalPlayer->GetAwardsData( kvAchInfo );
// for ( KeyValues *val = kvAchInfo->FindKey( "@achievements" )->GetFirstValue(); val; val = val->GetNextValue() )
// {
// int iVal = val->GetInt( "", 0 );
// if ( iVal <= 0 )
// continue;
// else if ( iVal < 64 )
// uiAchMask1 |= ( 1ull << iVal );
// else if ( iVal <= 100 )
// uiAchMask2 |= ( 1ull << ( iVal - 64 ) );
// }
// for ( KeyValues *val = kvAchInfo->FindKey( "@awards" )->GetFirstValue(); val; val = val->GetNextValue() )
// {
// int iVal = val->GetInt( "", 0 );
// if ( iVal <= 0 )
// continue;
// else if ( iVal < ( 128 - 100 ) )
// uiAchMask2 |= ( 1ull << ( iVal + 100 - 64 ) );
// }
//}
//cmd->SetUint64( "ach1", uiAchMask1 );
//cmd->SetUint64( "ach2", uiAchMask2 );
//if ( !V_stricmp( szReason, "datarequest" ) )
//{
// // Add game information
// TitleData1 * td1 = ( TitleData1 * ) pLocalPlayer->GetPlayerTitleData( TitleDataFieldsDescription_t::DB_TD1 );
// TitleData3 * td3 = ( TitleData3 * ) pLocalPlayer->GetPlayerTitleData( TitleDataFieldsDescription_t::DB_TD3 );
// // sp progress
// cmd->SetInt( "map_s", td1->uiSinglePlayerProgressChapter );
// // coop completion
// int numMapBitsFields = sizeof( td1->coop.mapbits ) / sizeof( uint64 );
// for ( int imap = 0; imap < numMapBitsFields; ++ imap )
// {
// cmd->SetUint64( CFmtStr( "map_%d", imap ), ( reinterpret_cast< uint64 * >( td1->coop.mapbits ) )[imap] );
// }
// if ( td3->cvUser.version )
// {
// // profile settings
// cmd->SetFloat( "cfg_pitch", td3->cvUser.joy_pitchsensitivity );
// cmd->SetFloat( "cfg_yaw", td3->cvUser.joy_yawsensitivity );
// cmd->SetInt( "cfg_joy", td3->cvUser.joy_cfg_preset );
// cmd->SetInt( "cfg_bit", td3->cvUser.bitfields[0] );
// }
// if ( td3->cvSystem.version )
// {
// // audio/video
// cmd->SetFloat( "sys_vol", td3->cvSystem.volume );
// cmd->SetFloat( "sys_mus", td3->cvSystem.snd_musicvolume );
// cmd->SetFloat( "sys_gam", td3->cvSystem.mat_monitorgamma );
// cmd->SetInt( "sys_ssm", td3->cvSystem.ss_splitmode );
// cmd->SetInt( "sys_bit", td3->cvSystem.bitfields[0] );
// }
// if ( g_pXboxInstaller )
// {
// cmd->SetInt( "inst",
// ( g_pXboxInstaller->IsFullyInstalled() ? 1 : 0 ) |
// ( g_pXboxInstaller->IsInstallEnabled() ? 2 : 0 )
// );
// }
//}
#endif
}
// Rolls up game details for matches grouping
KeyValues * CMatchTitleGameSettingsMgr::RollupGameDetails( KeyValues *pDetails, KeyValues *pRollup, KeyValues *pQuery ) { // TODO: keep each individual results, roll up to the party leader XUID
return NULL; }
// Defines dedicated server search key
KeyValues * CMatchTitleGameSettingsMgr::DefineDedicatedSearchKeys( KeyValues *pSettings, bool bNeedOfficialServer, int nSearchPass ) { #if defined ( _X360 )
return NULL;
#else
static ConVarRef sv_search_key( "sv_search_key" );
KeyValues *pKeys = new KeyValues( "SearchKeys" );
CFmtStr fmtExtraGameData;
if ( bNeedOfficialServer ) { pKeys->SetString( "gametype", "valve_ds,empty" ); } else { pKeys->SetString( "gametype", "empty" );
if ( char const *szMapGroup = pSettings->GetString( "game/mapgroupname", NULL ) ) { if ( char const *szWorkshop = Q_stristr( szMapGroup, "@workshop" ) ) { if ( szWorkshop != szMapGroup ) { // When searching for a workshop map only consider servers that are empty and advertise support for it
// and are running the same game type and mode as requested
// First search will be for explicitly supported map in rotation, but second search will be for
// servers that are willing to host public players
if ( 0 == ( nSearchPass % 2 ) ) { // First search pass: request map ID tag
fmtExtraGameData.AppendFormat( "%.*s,", szWorkshop - szMapGroup, szMapGroup ); } else { // Second search pass: request any map support
fmtExtraGameData.AppendFormat( "wks:1," ); }
// if ( !pSettings->GetBool( "options/anytypemode" ) ) -- maybe allow reserving community servers regardless of game modes?
{ int nGameType = 0, nGameMode = 0; g_pGameTypes->GetGameModeAndTypeIntsFromStrings( pSettings->GetString( "game/type" ), pSettings->GetString( "game/mode" ), nGameType, nGameMode ); fmtExtraGameData.AppendFormat( "gt:%u,gm:%u,", nGameType, nGameMode ); } } } } }
pKeys->SetString( "gamedata", CFmtStr( "%s%skey:%s%d", fmtExtraGameData.Access(), bNeedOfficialServer ? "v" : "c", sv_search_key.GetString(), g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() ) );
return pKeys;
#endif
}
static void DescribeX360QueryDefineSessionSearchKeys( KeyValues *pkv ) { #ifdef _X360
DevMsg( "======== DescribeX360QueryDefineSessionSearchKeys ==============\n" ); DevMsg( " numPlayers = %d\n", pkv->GetInt( "numPlayers" ) ); DevMsg( " rule = %s\n", ( pkv->GetInt( "rule" ) == SESSION_MATCH_QUERY_PLAYER_MATCH ) ? "playermatch" : "UNKNOWN" ); if ( KeyValues *val = pkv->FindKey( CFmtStr( "Contexts/%d", CONTEXT_CSS_GAME_TYPE ) ) ) { char const *szVal = "UNKNOWN"; switch ( val->GetInt() ) { case CONTEXT_CSS_GAME_TYPE_CLASSIC: szVal = "classic"; break; case CONTEXT_CSS_GAME_TYPE_GUNGAME: szVal = "gungame"; break; default: Assert( false ); break; } DevMsg( " CONTEXT_CSS_GAME_TYPE = %s (%d)\n", szVal, val->GetInt() ); }
if ( KeyValues *val = pkv->FindKey( CFmtStr( "Contexts/%d", CONTEXT_CSS_GAME_MODE ) ) ) { char const *szVal = "UNKNOWN"; switch ( val->GetInt() ) { case CONTEXT_CSS_GAME_MODE_CASUAL: szVal = "casual"; break; case CONTEXT_CSS_GAME_MODE_COMPETITIVE: szVal = "competitive"; break; case CONTEXT_CSS_GAME_MODE_FREESTYLE: szVal = "freestyle"; break; case CONTEXT_CSS_GAME_MODE_GUNGAMEPROGRESSIVE: szVal = "gungameprogressive"; break; case CONTEXT_CSS_GAME_MODE_GUNGAMEBOMB: szVal = "gungamebomb"; break; default: Assert( false ); break; } DevMsg( " CONTEXT_CSS_GAME_MODE = %s (%d)\n", szVal, val->GetInt() ); } else if ( KeyValues *val = pkv->FindKey( CFmtStr( "Properties/%d", PROPERTY_CSS_GAME_MODE_AS_NUMBER ) ) ) { DevMsg( " PROPERTY_CSS_GAME_MODE_AS_NUMBER near 0x%X (%d)\n", val->GetInt(), val->GetInt() ); }
if ( KeyValues *val = pkv->FindKey( CFmtStr( "Contexts/%d", CONTEXT_GAME_STATE ) ) ) { char const *szVal = "UNKNOWN"; switch ( val->GetInt() ) { case CONTEXT_GAME_STATE_IN_MENUS: szVal = "in_menus"; Assert( false ); break; case CONTEXT_GAME_STATE_SINGLE_PLAYER: szVal = "singleplayer"; Assert( false ); break; case CONTEXT_GAME_STATE_MULTIPLAYER: szVal = "multiplayer"; break; default: Assert( false ); break; } DevMsg( " CONTEXT_GAME_STATE = %s (%d)\n", szVal, val->GetInt() ); }
if ( KeyValues *val = pkv->FindKey( CFmtStr( "Contexts/%d", CONTEXT_CSS_MAP_GROUP ) ) ) { DevMsg( " CONTEXT_CSS_MAP_GROUP = %s (%d)\n", "", val->GetInt() ); }
if ( KeyValues *val = pkv->FindKey( CFmtStr( "Properties/%d", PROPERTY_CSS_MATCH_VERSION ) ) ) { DevMsg( " PROPERTY_CSS_MATCH_VERSION = 0x%X (%d)\n", val->GetInt(), val->GetInt() ); }
if ( KeyValues *val = pkv->FindKey( CFmtStr( "Properties/%d", PROPERTY_CSS_SEARCH_LISTEN_SERVER ) ) ) { DevMsg( " PROPERTY_CSS_SEARCH_LISTEN_SERVER = 0x%X (%d)\n", val->GetInt(), val->GetInt() ); }
if ( KeyValues *val = pkv->FindKey( CFmtStr( "Properties/%d", PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS ) ) ) { DevMsg( " PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS >= 0x%X (%d)\n", val->GetInt(), val->GetInt() ); }
if ( KeyValues *val = pkv->FindKey( CFmtStr( "Properties/%d", PROPERTY_CSS_AGGREGATE_SKILL0 ) ) ) { DevMsg( " PROPERTY_CSS_AGGREGATE_SKILL0 near 0x%X (%d)\n", val->GetInt(), val->GetInt() ); }
DWORD validsettings[] = { X_CONTEXT_GAME_TYPE, X_CONTEXT_GAME_MODE, CONTEXT_CSS_GAME_TYPE, CONTEXT_CSS_GAME_MODE, PROPERTY_CSS_GAME_MODE_AS_NUMBER, CONTEXT_GAME_STATE, CONTEXT_CSS_MAP_GROUP, PROPERTY_CSS_MATCH_VERSION, PROPERTY_CSS_SEARCH_LISTEN_SERVER, PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS, PROPERTY_CSS_AGGREGATE_SKILL0 }; char const *categories[] = { "Contexts", "Properties" }; for ( int iCategory = 0; iCategory < Q_ARRAYSIZE( categories ); ++ iCategory ) { for ( KeyValues *val = pkv->FindKey( categories[iCategory] )->GetFirstSubKey(); val; val = val->GetNextKey() ) { DWORD idx = atoi( val->GetName() ); bool bValid = false; for ( int jj = 0; jj < Q_ARRAYSIZE( validsettings ); ++ jj ) { if ( validsettings[jj] == idx ) { bValid = true; break; } } if ( !bValid ) { DevMsg( "Unexpected entry in query %s: %s (0x%X)\n", categories[iCategory], val->GetName(), idx ); } Assert( bValid ); } }
DevMsg( "======== ********X360Query*********************** ==============\n" ); #endif
}
// Defines session search keys for matchmaking
KeyValues * CMatchTitleGameSettingsMgr::DefineSessionSearchKeys( KeyValues *pSettings ) { MEM_ALLOC_CREDIT();
DevMsg( "DefineSessionSearchKeys settings:\n" ); KeyValuesDumpAsDevMsg( pSettings, 1 );
// Process the match system data here. This will bail early without loading if the
// input file from above doesn't bump it's version number.
LoadMatchSettings();
KeyValues *pResult = new KeyValues( "SessionSearch" );
int numPlayers = pSettings->GetInt( "members/numPlayers", XBX_GetNumGameUsers() ); pResult->SetInt( "numPlayers", numPlayers);
// Certain contexts and properties can not be part of the search parameters unless they are explictly
// required by the matchmaking query. Setting contexts/parameters that the query doesn't expect will
// cause the query to fail.
bool bCssMatchVersion = true; bool bCssLevel = true; bool bCssGameType = true; bool bCssGameMode = true; #if defined _X360
bool bCssGameModeAsNumber = true; #endif
bool bTeamMatch = true; bool bTeamMatchTypeClan = false; bool bTeamMatchTypeClanPreferred = false;
// Determine if this is a team matchmaking query:
// On Consoles we define "conteammatch" for team lobbies;
// On PC, we do NOT set bypasslobby for team lobbies
#if defined( _GAMECONSOLE )
bTeamMatch = ( pSettings->GetString( "options/conteammatch", NULL ) != NULL ); #else
bTeamMatch = ( pSettings->GetBool( "options/bypasslobby", false ) == false ); #endif
#if defined _X360
pResult->SetInt( "rule", rule ); #endif
// Set the appropriate query based on if we're quickmatching or custommatching.
char const *szAction = pSettings->GetString( "options/action", "" ); bool bMatchmakingQueryForGameInProgress = true; if ( !Q_stricmp( "quickmatch", szAction ) ) { bCssLevel = false; bMatchmakingQueryForGameInProgress = true; } else if ( !Q_stricmp( "custommatch", szAction ) ) { bMatchmakingQueryForGameInProgress = true; // If a mapgroup is specified use the player match query, but if no mapgroup is specified,
// use the player query that doesn't filter based on mapgroup name.
const char *pszMapGroupName = pSettings->GetString( "game/mapgroupname", NULL ); if ( pszMapGroupName != NULL && Q_strlen( pszMapGroupName ) > 0 ) { } else { bCssLevel = false; } }
#if defined _X360
// X_CONTEXT_GAME_TYPE
pResult->SetInt( CFmtStr( "Contexts/%d", X_CONTEXT_GAME_TYPE ), X_CONTEXT_GAME_TYPE_STANDARD ); pResult->SetInt( CFmtStr( "Contexts/%d", X_CONTEXT_GAME_MODE ), CONTEXT_GAME_MODE_CSS_GAME_MODE_MULTIPLAYER );
// X_CONTEXT_GAME_MODE
if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) ) { DWORD dwValue = g_GameModeContexts->ScanValues( szValue ); if ( bCssGameMode && dwValue != 0xFFFF ) { pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_CSS_GAME_MODE ), dwValue ); }
// Omit this property based on the rule.
// Set the PROPERTY_CSS_GAME_MODE_AS_NUMBER value as the square of the CONTEXT_CSS_GAME_MODE Difficulty setting.
// The resulting sequence of numbers (0, 1, 4, 9, ...) ensures that if an exact match can't be found
// then the 'near' sort operation will prefer the next easier match to the next harder match. For example,
// if COMPETITIVE is requested (1), then CASUAL matches (0) would be preferred to PRO matches (4).
dwValue = g_GameModeAsNumberContexts->ScanValues( szValue ); if ( bCssGameModeAsNumber && dwValue != 0xFFFF ) { pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_CSS_GAME_MODE_AS_NUMBER ), dwValue ); } }
// Set the game type (classic or gungame)
if ( char const *szGameType = pSettings->GetString( "game/type", NULL ) ) { DWORD dwValue = g_GameTypeContexts->ScanValues( szGameType ); if ( bCssGameType && dwValue != 0xFFFF ) { pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_CSS_GAME_TYPE ), dwValue ); } }
// Set the matchmaking version.
if ( bCssMatchVersion ) { pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_CSS_MATCH_VERSION ), mm_title_debug_version.GetInt() ); }
if ( 0 && mm_title_debug_minquery.GetBool() ) { DescribeX360QueryDefineSessionSearchKeys( pResult ); return pResult; // don't run the rest of filters, run with minimal set of parameters
}
// Set the mapgroup name we're using, if any.
if ( char const *szMapGroupName = pSettings->GetString( "game/mapgroupname", NULL ) ) { // First check if the rich presence context for this map was set in gamemodes.txt
DWORD dwValue = dwValue = pSettings->GetInt( "game/mapRichPresence", 0xFFFF ); if ( dwValue == 0xFFFF ) { dwValue = g_MapGroupContexts->ScanValues( szMapGroupName ); } if ( bCssLevel && dwValue != 0xFFFF ) { pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_CSS_MAP_GROUP ), dwValue ); } }
// Set desire to find games in lobbies
if ( bMatchmakingQueryForGameInProgress ) { pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_GAME_STATE ), CONTEXT_GAME_STATE_MULTIPLAYER ); }
#endif
// Determine if we're playing gungameprogressive mode so we can use the proper matchmaking data fields.
MatchmakingDataType mmDataType = MMDATA_TYPE_GENERAL; if ( !V_stricmp( "gungameprogressive", pSettings->GetString( "game/mode" ) ) ) { mmDataType = MMDATA_TYPE_GGPROGRESSIVE; }
// Add steam version of rule
AddSteamMatchmakingRule(pResult, !bMatchmakingQueryForGameInProgress, pSettings, bCssMatchVersion, bCssLevel, bCssGameType, bCssGameMode, bTeamMatch);
// If we want a clan-preferred match, duplicate search keys. First search for clan only
// matches and then regular matches
const char *szOppTeamType = pSettings->GetString( "game/opp_team_type", ""); bTeamMatchTypeClan = !Q_stricmp( "clan", szOppTeamType ); if ( !bTeamMatchTypeClan ) { bTeamMatchTypeClanPreferred = !Q_stricmp( "clan_preferred", szOppTeamType ); }
KeyValues *pTemplate = pResult->MakeCopy(); KeyValues::AutoDelete autoDeleteEvent( pTemplate );
if ( IsX360() ) { #ifdef _X360
if ( bConTeamMatch ) { pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS ), numPlayers ); } #endif
} else { if ( uint64 uiDependentLobby = pSettings->GetUint64( "System/dependentlobby", 0ull ) ) { pResult->SetUint64( "DependentLobby", uiDependentLobby ); } }
DescribeX360QueryDefineSessionSearchKeys( pResult ); return pResult; }
// Initializes full game settings from potentially abbreviated game settings
void CMatchTitleGameSettingsMgr::InitializeGameSettings( KeyValues *pSettings, const char *szReason ) { // GS - Make sure match settings are loaded. For team lobbies we create a lobby without first
// searching for one, so this function will be called before others (like DefineSessionSearchKeys)
// that also call LoadMatchSettings()
LoadMatchSettings();
//char const *szNetwork = pSettings->GetString( "system/network", "LIVE" );
if ( KeyValues *kv = pSettings->FindKey( "game", true ) ) { kv->SetString( "state", "lobby" ); }
const char *pMapGroupName = pSettings->GetString( "game/mapgroupname", NULL ); // if no mapgroup specified, randomly select a mapgroup based on type and mode
if ( !pMapGroupName ) { const char *pGameTypeName = pSettings->GetString( "game/type", NULL ); const char *pGameModeName = pSettings->GetString( "game/mode", NULL ); if ( pGameTypeName && pGameModeName ) { pMapGroupName = g_pGameTypes->GetRandomMapGroup( pGameTypeName, pGameModeName ); if ( pMapGroupName ) { pSettings->SetString( "game/mapgroupname", pMapGroupName ); } } }
// map name should not be coming in here, only mapgroup, so set mapname from mapgroupname
SetBspnameFromMapgroup( pSettings );
// Set the number of slots and the rich presence context based on the map.
const char *szMap = pSettings->GetString( "game/map", NULL ); int numSlots = pSettings->GetInt( "members/numSlots", 10 ); uint32 dwRichPresenceContext = 0xFFFF;
if ( szMap ) { g_pGameTypes->GetMapInfo( szMap, dwRichPresenceContext ); }
// If this is an official game, then we know how many slots we will force
if ( !Q_stricmp( "searchempty", pSettings->GetString( "options/createreason" ) ) && !pSettings->GetBool( "game/hosted" ) ) { const char *pGameTypeName = pSettings->GetString( "game/type" ); const char *pGameModeName = pSettings->GetString( "game/mode" ); int iType, iMode; if ( g_pGameTypes->GetGameModeAndTypeIntsFromStrings( pGameTypeName, pGameModeName, iType, iMode ) ) numSlots = g_pGameTypes->GetMaxPlayersForTypeAndMode( iType, iMode ); } pSettings->SetInt( "members/numSlots", numSlots );
#ifdef _X360
int extraSpectators = 2; pSettings->SetInt( "members/numExtraSpectatorSlots", extraSpectators ); pSettings->SetInt( "game/mapRichPresence", dwRichPresenceContext );
pSettings->SetInt( "game/matchversion", mm_title_debug_version.GetInt() ); pSettings->SetInt("game/experience", 0); #endif
if ( !IsX360() ) { // Add search key as a filter
static ConVarRef sv_search_key( "sv_search_key" ); CFmtStr searchKey( "k%s%d", sv_search_key.GetString(), g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() );
pSettings->SetString( "game/search_key", searchKey.Access()); // Temp: Check for sv_load_test
#if !defined (NO_STEAM)
if ( IsPC() && mm_sv_load_test.GetBool() ) { const char* playerCountry = steamapicontext->SteamUtils()->GetIPCountry(); if ( !Q_stricmp( playerCountry, "US") ) { pSettings->SetInt( "options/sv_load_test", 1 ); } }
#endif
}
if ( KeyValues *kvLocalPlayer = pSettings->FindKey( "members/machine0/player0" ) ) { #if !defined (NO_STEAM)
//
// Clan information
//
SplitScreenConVarRef varOption( "cl_clanid" ); const char *pClanID = varOption.GetString( 0 ); uint32 iPlayerClanID = atoi( pClanID );
kvLocalPlayer->SetInt( "game/clanID", iPlayerClanID ); ISteamFriends *pFriends = steamapicontext->SteamFriends(); if ( pFriends ) { int iGroupCount = pFriends->GetClanCount(); for ( int k = 0; k < iGroupCount; ++ k ) { CSteamID clanID = pFriends->GetClanByIndex( k ); if ( clanID.GetAccountID() == iPlayerClanID ) { CSteamID clanID( iPlayerClanID, steamapicontext->SteamUtils()->GetConnectedUniverse(), k_EAccountTypeClan ); // valid clan, accept the change
const char *szClanTag = pFriends->GetClanTag( clanID ); char chLimitedTag[ MAX_CLAN_TAG_LENGTH ]; CopyStringTruncatingMalformedUTF8Tail( chLimitedTag, szClanTag, MAX_CLAN_TAG_LENGTH ); const char *szClanName = pFriends->GetClanName( clanID );
kvLocalPlayer->SetString( "game/clantag", chLimitedTag ); kvLocalPlayer->SetString( "game/clanname", szClanName ); } } }
//
// Ticketing information for friends
//
if ( g_pMatchExtensions->GetIBaseClientDLL() ) { g_pMatchExtensions->GetIBaseClientDLL()->DetermineSubscriptionKvToAdvertise( kvLocalPlayer ); }
// If we are joining via friend discovery then pass it to the host
if ( uint64 xuidFriendJoin = pSettings->GetUint64( "options/friendxuid" ) ) { kvLocalPlayer->SetUint64( "game/jfriend", xuidFriendJoin ); }
// If we are joining via nearby discovery then pass it to the host
if ( int nNearbyJoin = pSettings->GetInt( "options/nby" ) ) { kvLocalPlayer->SetInt( "game/nby", nNearbyJoin ); }
// If we are joining via clan discovery then pass it to the host
if ( char const *szClanIdOption = pSettings->GetString( "options/clanid", NULL ) ) { kvLocalPlayer->SetString( "game/jclanid", szClanIdOption );
if ( pFriends ) { int iGroupCount = pFriends->GetClanCount(); for ( int k = 0; k < iGroupCount; ++k ) { CSteamID clanID = pFriends->GetClanByIndex( k ); if ( clanID.GetAccountID() == iPlayerClanID ) { CSteamID clanID( iPlayerClanID, steamapicontext->SteamUtils()->GetConnectedUniverse(), k_EAccountTypeClan ); // valid clan, accept the change
const char *szClanTag = pFriends->GetClanTag( clanID ); char chLimitedTag[ MAX_CLAN_TAG_LENGTH ]; CopyStringTruncatingMalformedUTF8Tail( chLimitedTag, szClanTag, MAX_CLAN_TAG_LENGTH ); kvLocalPlayer->SetString( "game/jclantag", chLimitedTag ); } } } }
// After we initialized local player transfer that data into session
pSettings->SetInt( "game/ark", kvLocalPlayer->GetInt( "game/ranking" )*10 ); pSettings->SetInt( "game/apr", kvLocalPlayer->GetInt( "game/prime" ) ); pSettings->SetString( "game/loc", kvLocalPlayer->GetString( "game/loc" ) ); #endif
} }
void CMatchTitleGameSettingsMgr::SetBspnameFromMapgroup( KeyValues *pSettings ) { const char *pMapGroupName = pSettings->GetString( "game/mapgroupname", NULL ); const char *pMapName = pSettings->GetString( "game/map", NULL ); if ( !pMapName && pMapGroupName ) { pMapName = g_pGameTypes->GetRandomMap( pMapGroupName ); if ( pMapName && pMapName[0] ) { pSettings->SetString( "game/map", pMapName ); } } }
// Extends game settings update packet before it gets merged with
// session settings and networked to remote clients
void CMatchTitleGameSettingsMgr::ExtendGameSettingsUpdateKeys( KeyValues *pSettings, KeyValues *pUpdateDeleteKeys ) { if ( char const *szClanIdUpdate = pUpdateDeleteKeys->GetString( "update/game/clanid", NULL ) ) { // Ensure that clantag is also set when setting clanid
if ( uint64 xuid = V_atoui64( szClanIdUpdate ) ) { CSteamID steamIdClan( xuid ); const char *pTag = steamapicontext->SteamFriends()->GetClanTag( steamIdClan ); if ( pTag && *pTag ) { char chLimitedTag[ MAX_CLAN_TAG_LENGTH ]; CopyStringTruncatingMalformedUTF8Tail( chLimitedTag, pTag, MAX_CLAN_TAG_LENGTH ); pUpdateDeleteKeys->SetString( "update/game/clantag", chLimitedTag ); } else { pUpdateDeleteKeys->SetString( "update/game/clantag", "" ); } } }
if ( char const *szClanIdUpdate = pUpdateDeleteKeys->GetString( "delete/game/clanid", NULL ) ) { pUpdateDeleteKeys->SetString( "delete/game/clantag", "" ); }
if ( char const *szMmQueueUpdate = pUpdateDeleteKeys->GetString( "update/game/mmqueue", NULL ) ) { int nAPR = pSettings->GetInt( "game/apr" ); int nUpdateAlready = pUpdateDeleteKeys->GetInt( "update/game/apr", -1 ); if ( nUpdateAlready >= 0 ) nAPR = nUpdateAlready; pUpdateDeleteKeys->SetInt( "update/game/apr", nAPR | 0x2 ); }
if ( char const *szMmQueueUpdate = pUpdateDeleteKeys->GetString( "delete/game/mmqueue", NULL ) ) { int nAPR = pSettings->GetInt( "game/apr" ); int nUpdateAlready = pUpdateDeleteKeys->GetInt( "update/game/apr", -1 ); if ( nUpdateAlready >= 0 ) nAPR = nUpdateAlready; pUpdateDeleteKeys->SetInt( "update/game/apr", nAPR & ~0x2 ); } }
KeyValues *CMatchTitleGameSettingsMgr::ExtendTeamLobbyToGame( KeyValues *pSettings ) { KeyValues *pUpdate = KeyValues::FromString( "update", " update { " " system { " " network LIVE " " netFlag #empty#" " } " " options { " " bypasslobby 1" " } " " game {" " } " " members {" " } " " } " );
// Add in bsp name from map group name
const char *pMapGroupName = pSettings->GetString( "game/mapgroupname", NULL ); Assert( pMapGroupName ); const char *pMapName = pSettings->GetString( "game/map", NULL ); Assert( pMapName ); if ( !pMapName ) { pMapName = g_pGameTypes->GetRandomMap( pMapGroupName ); pUpdate->SetString( "udpate/game/map", pMapName ); }
DevMsg( "CMatchTitleGameSettingsMgr::ExtendTeamLobbyToGame\n" ); KeyValuesDumpAsDevMsg( pUpdate );
return pUpdate; }
// Prepares system for session creation
KeyValues * CMatchTitleGameSettingsMgr::PrepareForSessionCreate( KeyValues *pSettings ) { // It's at this point that we need to set all of the appropriate properties on the local machine
// for matchmaking searches.
return MM_Title_RichPresence_PrepareForSessionCreate( pSettings ); }
// Prepares the lobby for game or adjust settings of new players who
// join a game in progress, this function is allowed to modify
// Members/Game subkeys and has to write modified players XUIDs
void CMatchTitleGameSettingsMgr::PrepareLobbyForGame( KeyValues *pSettings, KeyValues **ppPlayersUpdated ) { // set player avatar/teams, etc
}
// Prepares the host team lobby for game adjusting the game settings
// this function is allowed to prepare modification package to update
// Game subkeys.
// Returns the update/delete package to be applied to session settings
// and pushed to dependent two sesssion of the two teams.
KeyValues * CMatchTitleGameSettingsMgr::PrepareTeamLinkForGame( KeyValues *pSettingsLocal, KeyValues *pSettingsRemote ) { return NULL; }
void UpdateAggregateMembersSettings( KeyValues *pFullGameSettings, KeyValues *pUpdate ) { bool bAllPrime = true; int nAvgRank = 0; int nHaveRank = 0; int nTotalPlayers = 0;
char const *szBestCountry = ""; float flBestCountryWeight = 0.0f; CUtlStringMap< float > mapPlayerCountries; static CSteamID s_mysteamid = steamapicontext->SteamUser()->GetSteamID(); for ( int iMachine = 0, numMachines = pFullGameSettings->GetInt( "members/numMachines" ); iMachine < numMachines; ++iMachine ) { KeyValues *pMachine = pFullGameSettings->FindKey( CFmtStr( "members/machine%d", iMachine ) ); for ( int iPlayer = 0, numPlayers = pMachine->GetInt( "numPlayers" ); iPlayer < numPlayers; ++iPlayer ) { KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", iPlayer ) ); if ( !pPlayer ) continue;
if ( !pPlayer->GetInt( "game/prime" ) ) bAllPrime = false;
int nRanking = pPlayer->GetInt( "game/ranking" ); if ( nRanking ) { nAvgRank += nRanking; ++ nHaveRank; } ++ nTotalPlayers;
char const *szLocation = pPlayer->GetString( "game/loc" ); if ( !*szLocation ) continue;
UtlSymId_t symid = mapPlayerCountries.Find( szLocation ); if ( symid == UTL_INVAL_SYMBOL ) symid = mapPlayerCountries.Insert( szLocation, 0.0f ); float flNewWeightOfThisCountry = ( mapPlayerCountries[symid] += ( 1.0f + ( ( CSteamID( pPlayer->GetUint64( "xuid" ) ).GetAccountID() == s_mysteamid.GetAccountID() ) ? 0.5f : 0.0f ) ) ); if ( flNewWeightOfThisCountry > flBestCountryWeight ) { szBestCountry = szLocation; flBestCountryWeight = flNewWeightOfThisCountry; } } }
if ( !nAvgRank ) { nAvgRank = 7 * 10; // Nova II
} else if ( nHaveRank == nTotalPlayers ) { nAvgRank = ( nAvgRank * 10 ) / nHaveRank; } else { int nAvgRankedPeople = ( nAvgRank * 10 ); for ( ; nHaveRank < nTotalPlayers; ++ nHaveRank ) { nAvgRankedPeople += ( nAvgRankedPeople * 9 / nHaveRank / 10 ); } nAvgRank = nAvgRankedPeople / nHaveRank; }
int nAPR = bAllPrime ? 1 : 0; if ( *pFullGameSettings->GetString( "game/mmqueue" ) ) nAPR |= 0x2;
int numSlots = 5; if ( !V_stricmp( pFullGameSettings->GetString( "game/mode" ), "cooperative" ) ) numSlots = 2; if ( nTotalPlayers >= numSlots ) nAPR |= 0x4;
pUpdate->SetInt( "game/ark", nAvgRank ); pUpdate->SetInt( "game/apr", nAPR ); pUpdate->SetString( "game/loc", szBestCountry );
#ifdef _DEBUG
DevMsg( "UpdateAggregateMembersSettings: ark %d->%d, apr %d->%d, loc %s->%s\n", pFullGameSettings->GetInt( "game/ark" ), nAvgRank, pFullGameSettings->GetInt( "game/apr" ), nAPR, pFullGameSettings->GetString( "game/loc" ), szBestCountry ); #endif
}
// Executes the command on the session settings, this function on host
// is allowed to modify Members/Game subkeys and has to fill in modified players KeyValues
// When running on a remote client "ppPlayersUpdated" is NULL and players cannot
// be modified
void CMatchTitleGameSettingsMgr::ExecuteCommand( KeyValues *pCommand, KeyValues *pSessionSystemData, KeyValues *pSettings, KeyValues **ppPlayersUpdated ) { char const *szCommand = pCommand->GetName();
if ( !Q_stricmp( "Game::SetPlayerRanking", szCommand ) ) { if ( !Q_stricmp( "host", pSessionSystemData->GetString( "type", "host" ) ) && // !Q_stricmp( "lobby", pSessionSystemData->GetString( "state", "lobby" ) ) && - avatars also update when players change team ingame
ppPlayersUpdated ) { XUID xuidPlayer = pCommand->GetUint64( "xuid" );
// We know that the current session is the host. Validate that it is either updating itself, or a remote user
// is updating their own data
if ( !pSessionSystemData // offline session OK
|| ( ( xuidPlayer == pSessionSystemData->GetUint64( "xuidHost" ) ) && !pCommand->GetUint64( "_remote_xuidsrc" ) ) // host player update issued locally
|| ( xuidPlayer == pCommand->GetUint64( "_remote_xuidsrc" ) ) // remote command on behalf of matching XUID
) ; // ok to process this update
else return;
// Find the layer that is going to be updated
KeyValues *pPlayer = SessionMembersFindPlayer( pSettings, xuidPlayer ); if ( !pPlayer ) return;
KeyValues *kvGameKey = pCommand->FindKey( "game" ); if ( pPlayer && kvGameKey ) { KeyValues *kvPlayerGame = pPlayer->FindKey( "game", true ); if ( !kvPlayerGame ) return;
kvPlayerGame->MergeFrom( kvGameKey, KeyValues::MERGE_KV_UPDATE );
// Notify the sessions of a player update
* ( ppPlayersUpdated ++ ) = pPlayer;
// Also recompute aggregate session fields
if ( IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession() ) { KeyValues *kvPackage = new KeyValues( "Update" ); if ( KeyValues *kvUpdate = kvPackage->FindKey( "update", true ) ) { UpdateAggregateMembersSettings( pMatchSession->GetSessionSettings(), kvUpdate ); } pMatchSession->UpdateSessionSettings( KeyValues::AutoDeleteInline( kvPackage ) ); } } return; } } }
void CMatchTitleGameSettingsMgr::LoadMatchSettings( void ) { // Only load the match settings once.
if ( m_pMatchSystemData ) { return; }
m_pMatchSystemData = new KeyValues( "" );
// Load the match system keyvalues file if we haven't already.
if ( !m_pMatchSystemData->LoadFromFile( g_pFullFileSystem, "resource\\MatchSystem.res", "GAME" ) ) { m_pMatchSystemData->deleteThis(); m_pMatchSystemData = NULL; }
if ( !m_pMatchSystemData ) return;
// Get the version of the match file and compare to our latest parsed version.
// Will be used for PROPERTY_CSS_MATCH_VERSION.
int version = m_pMatchSystemData->GetInt( "version", -1 ); if ( ( mm_title_debug_version.GetInt() < 100 ) && ( mm_title_debug_version.GetInt() >= version ) ) { // Version file is not newer, so use what we already have.
AssertMsg( mm_title_debug_version.GetInt() >= 0, "Failed to load any match settings. Matchmaking will likely fail." ); return; } if ( mm_title_debug_version.GetInt() < 100 ) mm_title_debug_version.SetValue( version );
// Remove all previous formulas.
m_FormulaExperience.Purge();
// Load the experience formula.
KeyValues *pExperienceFormula = m_pMatchSystemData->FindKey( "ExperienceFormula" ); if ( pExperienceFormula ) { // Search for keys that are a number that corresponds to the skill number they should be used for.
for ( int nSkillIndex=0; ; ++nSkillIndex ) { char const *pszFormula = pExperienceFormula->GetString( CFmtStr( "%d", nSkillIndex ), NULL ); if ( !pszFormula || !*pszFormula ) { // No more formulas specified.
break; } m_FormulaExperience.AddToTail( pszFormula ); }
// Get the min/max range for valid values for these formulas.
m_nFormulaExperienceRangeMin = pExperienceFormula->GetInt( "minvalue", 0 ); m_nFormulaExperienceRangeMax = pExperienceFormula->GetInt( "maxvalue", INT_MAX ); }
// Remove all previous formulas.
for ( int i=0; i<m_FormulaSkill.Count(); ++i ) { delete m_FormulaSkill[i]; } m_FormulaSkill.Purge();
// Load the skill formulas.
for ( int nSkillFormulaIndex=0; ; ++nSkillFormulaIndex ) { KeyValues *pSkillFormulaInfo = m_pMatchSystemData->FindKey( CFmtStr( "Skill%dFormula", nSkillFormulaIndex ) ); if ( !pSkillFormulaInfo ) { // No more formulas specified.
break; }
SkillFormulas *pSkillFormulas = new SkillFormulas();
// Search for keys that are a number that corresponds to the skill number they should be used for.
for ( int nSkillIndex=0; ; ++nSkillIndex ) { char const *pszFormula = pSkillFormulaInfo->GetString( CFmtStr( "%d", nSkillIndex ), NULL ); if ( !pszFormula || !*pszFormula ) { // No more formulas specified.
break; } pSkillFormulas->formulas.AddToTail( pszFormula ); }
// Get the min/max range for valid values for these formulas.
pSkillFormulas->rangeMin = pSkillFormulaInfo->GetInt( "minvalue", 0 ); pSkillFormulas->rangeMax = pSkillFormulaInfo->GetInt( "maxvalue", INT_MAX );
m_FormulaSkill.AddToTail( pSkillFormulas ); }
// Load the average formula.
KeyValues *pAverageFormula = m_pMatchSystemData->FindKey( "AvgFormula" ); if ( pAverageFormula ) { // Search for keys that are a number that corresponds to the skill number they should be used for.
for ( int nSkillIndex=0; ; ++nSkillIndex ) { char const *pszFormula = pAverageFormula->GetString( CFmtStr( "%d", nSkillIndex ), NULL ); if ( !pszFormula || !*pszFormula ) { // No more formulas specified.
break; } m_FormulaAverage.AddToTail( pszFormula ); } }
// Remove all previous search passes.
for ( int i=0; i<m_SearchPass.Count(); ++i ) { delete m_SearchPass[i]; } m_SearchPass.Purge();
// Load the search passes.
for ( int nSearchPassIndex=0; ; ++nSearchPassIndex ) { KeyValues *pSearchPassInfo = m_pMatchSystemData->FindKey( CFmtStr( "SearchPass%d", nSearchPassIndex ) ); if ( !pSearchPassInfo ) { // No more passes specified.
break; }
SearchPass *pSearchPass = new SearchPass();
// Get the experience checks for this search pass.
pSearchPass->checkExperience = ( pSearchPassInfo->GetInt( "ExpCheck", 0 ) != 0 ); pSearchPass->experienceRange = pSearchPassInfo->GetInt( "ExperienceRange", 0 );
// Loop over all of the possible Skill#Check and Skill#Range entries
// so we can store their values for quick reference.
for ( int nSkillIndex=0; nSkillIndex<MATCH_MAX_SKILL_FIELDS; ++nSkillIndex ) { pSearchPass->checkSkill.AddToTail( ( pSearchPassInfo->GetInt( CFmtStr( "Skill%dCheck", nSkillIndex ), 0 ) != 0 ) ); pSearchPass->skillRange.AddToTail( pSearchPassInfo->GetInt( CFmtStr( "Skill%dRange", nSkillIndex ), 0 ) ); }
m_SearchPass.AddToTail( pSearchPass ); } }
// Retrieves the indexed formula from the match system settings file. (MatchSystem.360.res)
char const * CMatchTitleGameSettingsMgr::GetFormulaAverage( int index ) { // Ensure the matchmaking settings are loaded.
LoadMatchSettings();
if ( !m_FormulaAverage.Count() ) return "newValue";
int indexClamped = clamp( index, 0, m_FormulaAverage.Count() - 1 ); return m_FormulaAverage[ indexClamped ].String(); }
// Add Steam version of X360 matchmaking rules. Writes to pResult
void CMatchTitleGameSettingsMgr::AddSteamMatchmakingRule( KeyValues *pResult, bool bAllSessions, KeyValues *pSettings, bool bCssMatchVersion, bool bCssLevel, bool bCssGameType, bool bCssGameMode, bool bTeamMatch ) { // Check for official server
char const *serverType = pSettings->GetString( "options/server", ""); if ( !Q_stricmp( serverType, "official" )) { pResult->SetString( "Filter=/options:server", serverType); }
// Determine whether we are looking for community games or not
int iHosted = pSettings->GetInt( "game/hosted", 0 ); pResult->SetInt( "Filter=/game:hosted", iHosted ); int minPlayers = mm_csgo_community_search_players_min.GetInt();
// even if the key is not set on the lobby the MMS code for numerical compares
// must pass the check due to comparing it against default zero value
// Check for sv_search_key
char const *searchKey = pSettings->GetString( "game/search_key", NULL ); if ( searchKey ) { pResult->SetString( "Filter=/game:search_key", searchKey ); }
// Set up key values that are common across SESSION_MATCH_QUERY_PLAYER_MATCH
// and SESSION_MATCH_QUERY_PLAYER_MATCH_ANY_LEVEL
if ( !bAllSessions ) { const char *pMapGroupName = pSettings->GetString( "game/mapgroupname", NULL ); if ( pMapGroupName && strstr( pMapGroupName, "@workshop" ) && pSettings->GetBool( "options/anytypemode" ) ) { bCssGameMode = false; bCssGameType = false; }
// Game mode
if (bCssGameMode) { char const *gameMode = pSettings->GetString( "game/mode", NULL ); AssertMsg(gameMode, "Matchmaking: Rule SESSION_MATCH_QUERY_PLAYER_MATCH - no game mode; ignoring this filter"); if ( gameMode ) { pResult->SetString("Filter=/game:mode", gameMode); } }
#ifdef _X360
// Matchmaking rules version
if (bCssMatchVersion) { pResult->SetInt("Filter=/game:matchversion", mm_title_debug_version.GetInt() ); } #endif
// Privacy
char const *privacy = pSettings->GetString( "system/access", NULL ); AssertMsg(privacy, "Matchmaking: Rule SESSION_MATCH_QUERY_PLAYER_MATCH - no access mode; ignoring this filter"); if ( privacy ) { pResult->SetString("Filter=/system:access", privacy); }
// Game type
if (bCssGameType) { char const *gameType = pSettings->GetString( "game/type", NULL ); //AssertMsg(gameType, "Matchmaking: Rule SESSION_MATCH_QUERY_PLAYER_MATCH - no game type; ignoring this filter");
if (gameType) { pResult->SetString("Filter=/game:type", gameType); } } }
// Map name
if (bCssLevel) { const char *pMapGroupName = pSettings->GetString( "game/mapgroupname", NULL ); //AssertMsg(pMapGroupName, "Matchmaking: Rule SESSION_MATCH_QUERY_PLAYER_MATCH - no mapgroup name; ignoring this filter");
if ( pMapGroupName ) { // Check for workshop map matchmaking case
if ( char const *pszWorkshopMapGroup = strstr( pMapGroupName, "@workshop" ) ) { // search based on workshop map group regardless of the collection
pResult->SetString( "Filter=/game:map", pszWorkshopMapGroup + 1 ); minPlayers = 0; // when searching for a workshop map don't require min players
} else { pResult->SetString("Filter=/game:mapgroupname", pMapGroupName); } } }
if ( iHosted && ( minPlayers > 0 ) ) { pResult->SetInt( "Filter>=/members:numPlayers", minPlayers ); } else { pResult->SetInt( "Filter</members:numPlayers", 5 ); // always look for lobbies that are not full
}
if ( const char *pGameState = pSettings->GetString( "game/state", NULL ) ) { pResult->SetString( "Filter=/game:state", pGameState ); }
if ( KeyValues *kvAllPrime = pSettings->FindKey( "game/apr" ) ) { pResult->SetInt( "Filter=/game:apr", kvAllPrime->GetInt() ); }
if ( KeyValues *kvNearRank = pSettings->FindKey( "game/ark" ) ) { if ( !kvNearRank->GetInt() ) { pResult->SetInt( "Filter>=/game:ark", 7*10 ); // Nova 1
pResult->SetInt( "Filter<=/game:ark", 12*10 ); // MG II
pResult->SetInt( "Near/game:ark", 9*10 ); // Nova III
} else { pResult->SetInt( "Filter>=/game:ark", ( kvNearRank->GetInt() - 2 ) * 10 ); pResult->SetInt( "Filter<=/game:ark", ( kvNearRank->GetInt() + 2 ) * 10 ); pResult->SetInt( "Near/game:ark", kvNearRank->GetInt()*10 ); } pResult->SetInt( "Near/members:numPlayers", 5 ); // always look for lobbies that are close to full
}
if ( const char *pMMQueue = pSettings->GetString( "game/mmqueue", NULL ) ) { pResult->SetString( "Filter=/game:mmqueue", pMMQueue ); }
if ( const char *szClanID = pSettings->GetString( "game/clanid", NULL ) ) { pResult->SetString( "Filter=/game:clanid", szClanID ); } }
// Called by the client to notify matchmaking that it should update matchmaking properties based
// on player distribution among the teams.
void CMatchTitleGameSettingsMgr::UpdateTeamProperties( KeyValues *pCurrentSettings, KeyValues *pTeamProperties ) { MM_Title_RichPresence_UpdateTeamPropertiesCSGO( pCurrentSettings, pTeamProperties ); }
#ifdef _X360
void MM_dumpcontextsandproperties( void ) { IXboxSystem *pXboxSystem = g_pMatchExtensions->GetIXboxSystem(); if ( pXboxSystem ) { DWORD userIndex = XBX_GetPrimaryUserId();
uint value = 0;
KeyValues *pkv = new KeyValues( "ContextsAndProperties" ); KeyValues::AutoDelete autoDeleteEvent( pkv );
pXboxSystem->UserGetContext( userIndex, X_CONTEXT_GAME_TYPE, value ); pkv->SetInt( CFmtStr( "Contexts/%d\t\t(X_CONTEXT_GAME_TYPE)", X_CONTEXT_GAME_TYPE ), value );
pXboxSystem->UserGetContext( userIndex, X_CONTEXT_GAME_MODE, value ); pkv->SetInt( CFmtStr( "Contexts/%d\t\t(X_CONTEXT_GAME_MODE)", X_CONTEXT_GAME_MODE ), value );
pXboxSystem->UserGetContext( userIndex, CONTEXT_CSS_LEVEL, value ); pkv->SetInt( CFmtStr( "Contexts/%d\t\t(CONTEXT_CSS_LEVEL)", CONTEXT_CSS_LEVEL ), value );
pXboxSystem->UserGetContext( userIndex, CONTEXT_CSS_MAP_GROUP, value ); pkv->SetInt( CFmtStr( "Contexts/%d\t\t(CONTEXT_CSS_MAP_GROUP)", CONTEXT_CSS_MAP_GROUP ), value );
pXboxSystem->UserGetContext( userIndex, CONTEXT_GAME_STATE, value ); pkv->SetInt( CFmtStr( "Contexts/%d\t\t(CONTEXT_GAME_STATE)", CONTEXT_GAME_STATE ), value );
pXboxSystem->UserGetContext( userIndex, CONTEXT_CSS_GAME_MODE, value ); pkv->SetInt( CFmtStr( "Contexts/%d\t\t(CONTEXT_CSS_GAME_MODE)", CONTEXT_CSS_GAME_MODE ), value );
pXboxSystem->UserGetContext( userIndex, CONTEXT_CSS_TEAM, value ); pkv->SetInt( CFmtStr( "Contexts/%d\t\t(CONTEXT_CSS_TEAM)", CONTEXT_CSS_TEAM ), value );
pXboxSystem->UserGetContext( userIndex, CONTEXT_CSS_PRIVACY, value ); pkv->SetInt( CFmtStr( "Context/%d\t\t(CONTEXT_CSS_PRIVACY)", CONTEXT_CSS_PRIVACY ), value );
pXboxSystem->UserGetContext( userIndex, CONTEXT_CSS_GAME_TYPE, value ); pkv->SetInt( CFmtStr( "Context/%d\t\t(CONTEXT_CSS_GAME_TYPE)", CONTEXT_CSS_GAME_TYPE ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_AGGREGATE_EXPERIENCE, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_AGGREGATE_EXPERIENCE)", PROPERTY_CSS_AGGREGATE_EXPERIENCE ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_AGGREGATE_SKILL0, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_AGGREGATE_SKILL0)", PROPERTY_CSS_AGGREGATE_SKILL0 ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_AGGREGATE_SKILL1, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_AGGREGATE_SKILL1)", PROPERTY_CSS_AGGREGATE_SKILL1 ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_AGGREGATE_SKILL2, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_AGGREGATE_SKILL2)", PROPERTY_CSS_AGGREGATE_SKILL2 ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_AGGREGATE_SKILL3, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_AGGREGATE_SKILL3)", PROPERTY_CSS_AGGREGATE_SKILL3 ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_AGGREGATE_SKILL4, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_AGGREGATE_SKILL4)", PROPERTY_CSS_AGGREGATE_SKILL4 ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_MATCH_VERSION, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_MATCH_VERSION)", PROPERTY_CSS_MATCH_VERSION ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_OPEN_SLOTS, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_OPEN_SLOTS)", PROPERTY_CSS_OPEN_SLOTS ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_EXP_MAX, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_EXP_MAX)", PROPERTY_CSS_SEARCH_EXP_MAX ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_EXP_MIN, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_EXP_MIN)", PROPERTY_CSS_SEARCH_EXP_MIN ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_SKILL0_MAX, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_SKILL0_MAX)", PROPERTY_CSS_SEARCH_SKILL0_MAX ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_SKILL0_MIN, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_SKILL0_MIN)", PROPERTY_CSS_SEARCH_SKILL0_MIN ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_SKILL1_MAX, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_SKILL1_MAX)", PROPERTY_CSS_SEARCH_SKILL1_MAX ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_SKILL1_MIN, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_SKILL1_MIN)", PROPERTY_CSS_SEARCH_SKILL1_MIN ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_SKILL2_MAX, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_SKILL2_MAX)", PROPERTY_CSS_SEARCH_SKILL2_MAX ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_SKILL2_MIN, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_SKILL2_MIN)", PROPERTY_CSS_SEARCH_SKILL2_MIN ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_SKILL3_MAX, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_SKILL3_MAX)", PROPERTY_CSS_SEARCH_SKILL3_MAX ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_SKILL3_MIN, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_SKILL3_MIN)", PROPERTY_CSS_SEARCH_SKILL3_MIN ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_SKILL4_MAX, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_SKILL4_MAX)", PROPERTY_CSS_SEARCH_SKILL4_MAX ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_SEARCH_SKILL4_MIN, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_SEARCH_SKILL4_MIN)", PROPERTY_CSS_SEARCH_SKILL4_MIN ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_GAME_MODE_AS_NUMBER, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_GAME_MODE_AS_NUMBER)", PROPERTY_CSS_GAME_MODE_AS_NUMBER ), value );
pXboxSystem->UserGetPropertyInt( userIndex, PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS, value ); pkv->SetInt( CFmtStr( "Properties/%d\t\t(PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS)", PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS ), value );
KeyValuesDumpAsDevMsg( pkv ); } }
static ConCommand mm_dumpcontextsandproperties("mm_dumpcontextsandproperties", MM_dumpcontextsandproperties, "Dump the current values for all of the title's contexts and properties.", FCVAR_DEVELOPMENTONLY ); #endif // _X360
|