This commit is contained in:
nephacks
2025-06-04 03:22:50 +02:00
parent f234f23848
commit f12416cffd
14243 changed files with 6446499 additions and 26 deletions

View File

@@ -0,0 +1,58 @@
// ----------------------------------------- //
// File generated by VPC //
// ----------------------------------------- //
Source file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_gamesettingsmgr.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_gamesettingsmgr.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_gamesettingsmgr.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_main.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_main.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_main.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_richpresence.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_richpresence.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_richpresence.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_titledata.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_titledata.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_titledata.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\game\shared\cstrike15\gametypes.cpp
Debug output file: F:\csgo_64\cstrike15_src\game\shared\cstrike15\gametypes.cpp
Release output file: F:\csgo_64\cstrike15_src\game\shared\cstrike15\gametypes.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\common\platforminputdevice.cpp
Debug output file: F:\csgo_64\cstrike15_src\common\platforminputdevice.cpp
Release output file: F:\csgo_64\cstrike15_src\common\platforminputdevice.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\common\debug_dll_check.cpp
Debug output file: F:\csgo_64\cstrike15_src\common\debug_dll_check.cpp
Release output file: F:\csgo_64\cstrike15_src\common\debug_dll_check.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\public\tier0\memoverride.cpp
Debug output file: F:\csgo_64\cstrike15_src\public\tier0\memoverride.cpp
Release output file: F:\csgo_64\cstrike15_src\public\tier0\memoverride.cpp
Containing unity file:
PCH file:

View File

@@ -0,0 +1,58 @@
// ----------------------------------------- //
// File generated by VPC //
// ----------------------------------------- //
Source file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_gamesettingsmgr.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_gamesettingsmgr.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_gamesettingsmgr.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_main.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_main.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_main.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_richpresence.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_richpresence.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_richpresence.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_titledata.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_titledata.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\cstrike15\mm_title_titledata.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\game\shared\cstrike15\gametypes.cpp
Debug output file: F:\csgo_64\cstrike15_src\game\shared\cstrike15\gametypes.cpp
Release output file: F:\csgo_64\cstrike15_src\game\shared\cstrike15\gametypes.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\common\platforminputdevice.cpp
Debug output file: F:\csgo_64\cstrike15_src\common\platforminputdevice.cpp
Release output file: F:\csgo_64\cstrike15_src\common\platforminputdevice.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\common\debug_dll_check.cpp
Debug output file: F:\csgo_64\cstrike15_src\common\debug_dll_check.cpp
Release output file: F:\csgo_64\cstrike15_src\common\debug_dll_check.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\public\tier0\memoverride.cpp
Debug output file: F:\csgo_64\cstrike15_src\public\tier0\memoverride.cpp
Release output file: F:\csgo_64\cstrike15_src\public\tier0\memoverride.cpp
Containing unity file:
PCH file:

View File

@@ -0,0 +1,202 @@
// ----------------------------------------- //
// File generated by VPC //
// ----------------------------------------- //
Source file: F:\csgo_64\cstrike15_src\matchmaking\matchmakingqos.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\matchmakingqos.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\matchmakingqos.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_events.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_events.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_events.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_extensions.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_extensions.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_extensions.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_framework.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_framework.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_framework.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmsgcontroller.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmsgcontroller.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmsgcontroller.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_session.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_voice.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_voice.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_voice.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\steam_apihook.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\steam_apihook.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\steam_apihook.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\steam_datacenterjobs.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\steam_datacenterjobs.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\steam_datacenterjobs.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\steam_lobbyapi.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\steam_lobbyapi.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\steam_lobbyapi.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\x360_xlsp_cmd.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\x360_xlsp_cmd.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\x360_xlsp_cmd.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\ds_searcher.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\ds_searcher.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\ds_searcher.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\match_searcher.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\match_searcher.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\match_searcher.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmgr.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmgr.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmgr.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_offline_custom.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_offline_custom.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_offline_custom.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_client.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_client.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_client.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_host.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_host.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_host.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_search.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_search.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_search.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_teamsearch.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_teamsearch.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_session_online_teamsearch.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\sys_session.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\sys_session.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\sys_session.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\common\debug_lib_check.cpp
Debug output file: F:\csgo_64\cstrike15_src\common\debug_lib_check.cpp
Release output file: F:\csgo_64\cstrike15_src\common\debug_lib_check.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\public\filesystem_helpers.cpp
Debug output file: F:\csgo_64\cstrike15_src\public\filesystem_helpers.cpp
Release output file: F:\csgo_64\cstrike15_src\public\filesystem_helpers.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\main.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\main.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\main.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\datacenter.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\datacenter.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\datacenter.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\leaderboards.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\leaderboards.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\leaderboards.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\matchsystem.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\matchsystem.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\matchsystem.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_dlc.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_dlc.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_dlc.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\player.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\player.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\player.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\playermanager.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\playermanager.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\playermanager.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\playerrankingdata.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\playerrankingdata.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\playerrankingdata.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\searchmanager.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\searchmanager.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\searchmanager.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\servermanager.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\servermanager.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\servermanager.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\extkeyvalues.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\extkeyvalues.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\extkeyvalues.cpp
Containing unity file:
PCH file:

View File

@@ -0,0 +1,130 @@
// ----------------------------------------- //
// File generated by VPC //
// ----------------------------------------- //
Source file: F:\csgo_64\cstrike15_src\matchmaking\matchmakingqos.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\matchmakingqos.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\matchmakingqos.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_events.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_events.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_events.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_extensions.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_extensions.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_extensions.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_framework.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_framework.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_framework.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmsgcontroller.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmsgcontroller.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmsgcontroller.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_voice.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_voice.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_voice.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\steam_apihook.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\steam_apihook.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\steam_apihook.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\steam_datacenterjobs.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\steam_datacenterjobs.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\steam_datacenterjobs.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\steam_lobbyapi.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\steam_lobbyapi.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\steam_lobbyapi.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\x360_xlsp_cmd.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\x360_xlsp_cmd.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\x360_xlsp_cmd.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmgr.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmgr.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_netmgr.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\common\debug_lib_check.cpp
Debug output file: F:\csgo_64\cstrike15_src\common\debug_lib_check.cpp
Release output file: F:\csgo_64\cstrike15_src\common\debug_lib_check.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\public\filesystem_helpers.cpp
Debug output file: F:\csgo_64\cstrike15_src\public\filesystem_helpers.cpp
Release output file: F:\csgo_64\cstrike15_src\public\filesystem_helpers.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\main.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\main.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\main.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\datacenter.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\datacenter.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\datacenter.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\matchsystem.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\matchsystem.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\matchsystem.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\mm_dlc.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\mm_dlc.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\mm_dlc.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\playermanager.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\playermanager.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\playermanager.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\playerrankingdata.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\playerrankingdata.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\playerrankingdata.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\servermanager.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\servermanager.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\servermanager.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\matchmaking\extkeyvalues.cpp
Debug output file: F:\csgo_64\cstrike15_src\matchmaking\extkeyvalues.cpp
Release output file: F:\csgo_64\cstrike15_src\matchmaking\extkeyvalues.cpp
Containing unity file:
PCH file:

View File

@@ -0,0 +1,510 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "mm_title_richpresence.h"
#include "csgo.spa.h"
#ifdef _PS3
#include <netex/net.h>
#include <netex/libnetctl.h>
#endif
#include "fmtstr.h"
#include "gametypes/igametypes.h"
#include "netmessages_signon.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern IGameTypes *g_pGameTypes;
CMatchTitle::CMatchTitle()
{
}
CMatchTitle::~CMatchTitle()
{
}
//
// Init / shutdown
//
InitReturnVal_t CMatchTitle::Init()
{
if ( IGameEventManager2 *mgr = g_pMatchExtensions->GetIGameEventManager2() )
{
mgr->AddListener( this, "server_pre_shutdown", false );
mgr->AddListener( this, "game_newmap", false );
mgr->AddListener( this, "finale_start", false );
mgr->AddListener( this, "round_start", false );
mgr->AddListener( this, "round_end", false );
mgr->AddListener( this, "difficulty_changed", false );
mgr->AddListener( this, "player_connect", false );
mgr->AddListener( this, "player_disconnect", false );
}
#ifdef _GAMECONSOLE
// Initialize Title Update version
extern ConVar mm_tu_string;
mm_tu_string.SetValue( "00000000" );
#endif
g_pGameTypes->Initialize();
return INIT_OK;
}
void CMatchTitle::Shutdown()
{
if ( IGameEventManager2 *mgr = g_pMatchExtensions->GetIGameEventManager2() )
{
mgr->RemoveListener( this );
}
}
//
// Implementation
//
uint64 CMatchTitle::GetTitleID()
{
#ifdef _X360
return TITLEID_COUNTER_STRIKE__GO;
#elif !defined( SWDS ) && !defined( NO_STEAM )
static uint64 uiAppID = 0ull;
if ( !uiAppID && steamapicontext && steamapicontext->SteamUtils() )
{
uiAppID = steamapicontext->SteamUtils()->GetAppID();
}
return uiAppID;
#else
return 0ull;
#endif
}
uint64 CMatchTitle::GetTitleServiceID()
{
#ifdef _X360
return 0x45410880ull; // Left 4 Dead 1 Service ID
#else
return 0ull;
#endif
}
#ifdef _PS3
uint64 CMatchTitle::GetTitleSettingsFlags()
{
return MATCHTITLE_SETTING_MULTIPLAYER
| MATCHTITLE_VOICE_INGAME
| MATCHTITLE_FORCE_PSN_NAMES
| MATCHTITLE_PLAYERMGR_DISABLED
| MATCHTITLE_SERVERMGR_DISABLED;
}
#else
uint64 CMatchTitle::GetTitleSettingsFlags()
{
return MATCHTITLE_SETTING_MULTIPLAYER
| MATCHTITLE_VOICE_INGAME
| MATCHTITLE_PLAYERMGR_ALLFRIENDS
#if !defined( CSTRIKE15 )
| MATCHTITLE_SETTING_NODEDICATED
| MATCHTITLE_PLAYERMGR_DISABLED
| MATCHTITLE_SERVERMGR_DISABLED
| MATCHTITLE_INVITE_ONLY_SINGLE_USER
#else
| MATCHTITLE_PLAYERMGR_FRIENDREQS
#endif // !CSTRIKE15
;
}
#endif
#ifdef _PS3
void *g_pMatchTitle_NetMemory;
#endif
void CMatchTitle::PrepareNetStartupParams( void *pNetStartupParams )
{
#ifdef _X360
XNetStartupParams &xnsp = *( XNetStartupParams * ) pNetStartupParams;
xnsp.cfgQosDataLimitDiv4 = 64; // 256 bytes
// Increase outstanding QoS responses significantly
// See: https://forums.xboxlive.com/AnswerPage.aspx?qid=493b207b-66b9-42bc-b23d-ddc306e09749&tgt=1
xnsp.cfgQosSrvMaxSimultaneousResponses = 255;
xnsp.cfgSockDefaultRecvBufsizeInK = 64; // Increase receive size for UDP to 64k
xnsp.cfgSockDefaultSendBufsizeInK = 64; // Keep send size at 64k too
int numGamePlayersMax = GetTotalNumPlayersSupported();
int numConnections = 4 * ( numGamePlayersMax - 1 );
// - the max number of connections to members of your game party
// - the max number of connections to members of your social party
// - the max number of connections to a pending game party (if you are joining a new one ).
// - matchmakings client info structure also creates a connection per client for the lobby.
// 1 - the main game session
int numTotalConnections = 1 + numConnections;
// 29 - total Connections (XNADDR/XNKID pairs) ,using 5 sessions (XNKID/XNKEY pairs).
xnsp.cfgKeyRegMax = 16; //adding some extra room because of lazy dealocation of these pairs.
xnsp.cfgSecRegMax = MAX( 64, numTotalConnections ); //adding some extra room because of lazy dealocation of these pairs.
xnsp.cfgSockMaxDgramSockets = xnsp.cfgSecRegMax;
xnsp.cfgSockMaxStreamSockets = xnsp.cfgSecRegMax;
#endif
#if defined( _PS3 ) && defined( NO_STEAM )
MEM_ALLOC_CREDIT_( "NO_STEAM: CMatchTitle::PrepareNetStartupParams" );
sys_net_initialize_parameter_t &snip = *( sys_net_initialize_parameter_t * ) pNetStartupParams;
snip.memory_size = 512 * 1024;
snip.memory = malloc( snip.memory_size ); // alternatively this can be a global array
g_pMatchTitle_NetMemory = snip.memory; // bookmark the memory address for later inspection if necessary
#endif
}
int CMatchTitle::GetTotalNumPlayersSupported()
{
#ifdef _GAMECONSOLE
// [jason] Rounding this up to 16, since this was the value used on cstrike15 xbla. Among other things, this
// controls how many remote talker voice channels are reserved for each client in XHV
return 16;
#else
return 64; // On PC this is not limited, return max number dedicated servers can ever run with
#endif
}
// Get a guest player name
char const * CMatchTitle::GetGuestPlayerName( int iUserIndex )
{
if ( vgui::ILocalize *pLocalize = g_pMatchExtensions->GetILocalize() )
{
if ( wchar_t* wStringTableEntry = pLocalize->Find( "#SFUI_LocalPlayer" ) )
{
static char szName[ MAX_PLAYER_NAME_LENGTH ] = {0};
pLocalize->ConvertUnicodeToANSI( wStringTableEntry, szName, ARRAYSIZE( szName ) );
return szName;
}
}
return "";
}
// Sets up all necessary client-side convars and user info before
// connecting to server
void CMatchTitle::PrepareClientForConnect( KeyValues *pSettings )
{
#ifndef SWDS
int numPlayers = 1;
#ifdef _GAMECONSOLE
numPlayers = XBX_GetNumGameUsers();
#endif
//
// Now we set the convars
//
for ( int k = 0; k < numPlayers; ++ k )
{
int iController = k;
#ifdef _GAMECONSOLE
iController = XBX_GetUserId( k );
#endif
IPlayerLocal *pPlayerLocal = g_pPlayerManager->GetLocalPlayer( iController );
if ( !pPlayerLocal )
continue;
// Set "name"
static SplitScreenConVarRef s_cl_name( "name" );
char const *szName = pPlayerLocal->GetName();
s_cl_name.SetValue( k, szName );
// Set "networkid_force"
if ( IsX360() )
{
static SplitScreenConVarRef s_networkid_force( "networkid_force" );
uint64 xid = pPlayerLocal->GetXUID();
s_networkid_force.SetValue( k, CFmtStr( "%08X:%08X:", uint32( xid >> 32 ), uint32( xid ) ) );
}
}
#endif
}
bool CMatchTitle::StartServerMap( KeyValues *pSettings )
{
int numPlayers = 1;
#ifdef _GAMECONSOLE
numPlayers = XBX_GetNumGameUsers();
#endif
char const *szMap = pSettings->GetString( "game/map", NULL );
if ( !szMap )
return false;
// Check that we have the server interface and that the map is valid
if ( !g_pMatchExtensions->GetIVEngineServer() )
return false;
if ( !g_pMatchExtensions->GetIVEngineServer()->IsMapValid( szMap ) )
return false;
//
// Prepare game dll reservation package
//
KeyValues *pGameDllReserve = g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForReservation( pSettings );
KeyValues::AutoDelete autodelete( pGameDllReserve );
pGameDllReserve->SetString( "map/mapcommand", ( numPlayers <= 1 ) ? "map" : "ss_map" );
if ( !Q_stricmp( "commentary", pSettings->GetString( "options/play", "" ) ) )
pGameDllReserve->SetString( "map/mapcommand", "map_commentary" );
// Run map based off the faked reservation packet
g_pMatchExtensions->GetIVEngineClient()->StartLoadingScreenForKeyValues( pGameDllReserve );
return true;
}
static KeyValues * GetCurrentMatchSessionSettings()
{
IMatchSession *pIMatchSession = g_pMatchFramework->GetMatchSession();
return pIMatchSession ? pIMatchSession->GetSessionSettings() : NULL;
}
//
// <Vitaliy> July-2012
// CS:GO hack: training map has been implemented storing progress in convars instead of
// properly storing it in game profile data, force storing the convars into profile
// when the training session finishes here
// Please, do not use this approach in future code.
//
bool g_bPlayingTrainingMap = false;
void CMatchTitle::OnEvent( KeyValues *pEvent )
{
char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "OnPlayerRemoved", szEvent ) ||
!Q_stricmp( "OnPlayerUpdated", szEvent ) )
{
MM_Title_RichPresence_Update( GetCurrentMatchSessionSettings(), NULL );
}
else if ( !Q_stricmp( "OnPlayerMachinesConnected", szEvent ) ||
!Q_stricmp( "OnPlayerMachinesDisconnected", szEvent ) )
{
// Player counts changed on host, update aggregate fields
IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession();
if ( !pMatchSession )
return;
KeyValues *kvPackage = new KeyValues( "Update" );
if ( KeyValues *kvUpdate = kvPackage->FindKey( "update", true ) )
{
extern void UpdateAggregateMembersSettings( KeyValues *pFullGameSettings, KeyValues *pUpdate );
UpdateAggregateMembersSettings( pMatchSession->GetSessionSettings(), kvUpdate );
}
pMatchSession->UpdateSessionSettings( KeyValues::AutoDeleteInline( kvPackage ) );
}
else if ( !Q_stricmp( "OnMatchSessionUpdate", szEvent ) )
{
if ( !Q_stricmp( pEvent->GetString( "state" ), "updated" ) )
{
if ( KeyValues *kvUpdate = pEvent->FindKey( "update" ) )
{
MM_Title_RichPresence_Update( GetCurrentMatchSessionSettings(), kvUpdate );
}
}
else if ( !Q_stricmp( pEvent->GetString( "state" ), "created" ) ||
!Q_stricmp( pEvent->GetString( "state" ), "ready" ) )
{
MM_Title_RichPresence_Update( GetCurrentMatchSessionSettings(), NULL );
if ( IMatchSession *pSession = g_pMatchFramework->GetMatchSession() )
{
if ( !Q_stricmp( "training", pSession->GetSessionSettings()->GetString( "game/type" ) ) )
g_bPlayingTrainingMap = true;
}
}
else if ( !Q_stricmp( pEvent->GetString( "state" ), "closed" ) )
{
if ( g_bPlayingTrainingMap )
{
g_bPlayingTrainingMap = false;
g_pMatchExtensions->GetIVEngineClient()->ClientCmd_Unrestricted( CFmtStr( "host_writeconfig_ss %d", XBX_GetPrimaryUserId() ) );
}
MM_Title_RichPresence_Update( NULL, NULL );
}
}
else if ( !Q_stricmp( szEvent, "Client::CmdKeyValues" ) )
{
KeyValues *pCmd = pEvent->GetFirstTrueSubKey();
if ( !pCmd )
return;
char const *szCmd = pCmd->GetName();
if ( !Q_stricmp( "ExtendedServerInfo", szCmd ) )
{
KeyValuesDumpAsDevMsg( pCmd, 2, 1 );
g_pGameTypes->SetAndParseExtendedServerInfo( pCmd );
}
}
else if ( !Q_stricmp( "OnEngineClientSignonStateChange", szEvent ) )
{
int iOldState = pEvent->GetInt( "old", 0 );
int iNewState = pEvent->GetInt( "new", 0 );
if (
( iOldState >= SIGNONSTATE_CONNECTED && // disconnect
iNewState < SIGNONSTATE_CONNECTED ) ||
( iOldState < SIGNONSTATE_FULL && // full connect
iNewState >= SIGNONSTATE_FULL )
)
{
MM_Title_RichPresence_Update( NULL, NULL );
}
}
else if ( !Q_stricmp( "OnEngineDisconnectReason", szEvent ) )
{
MM_Title_RichPresence_Update( NULL, NULL );
}
else if ( !Q_stricmp( "OnEngineEndGame", szEvent ) )
{
MM_Title_RichPresence_Update( NULL, NULL );
}
}
//
//
//
int CMatchTitle::GetEventDebugID( void )
{
return EVENT_DEBUG_ID_INIT;
}
void CMatchTitle::FireGameEvent( IGameEvent *pIGameEvent )
{
// Parse the game event
char const *szGameEvent = pIGameEvent->GetName();
if ( !szGameEvent || !*szGameEvent )
return;
if ( !Q_stricmp( "round_start", szGameEvent ) ||
!Q_stricmp( "round_end", szGameEvent ) ||
!Q_stricmp( "game_newmap", szGameEvent ) ||
!Q_stricmp( "player_connect", szGameEvent ) ||
!Q_stricmp( "player_disconnect", szGameEvent ) )
{ // Update rich presence
MM_Title_RichPresence_Update( NULL, NULL );
}
// Check if the current match session is on an active game server
IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession();
if ( !pMatchSession )
return;
KeyValues *pSessionSettings = pMatchSession->GetSessionSettings();
char const *szGameServer = pSessionSettings->GetString( "server/server", "" );
char const *szSystemLock = pSessionSettings->GetString( "system/lock", "" );
if ( ( !szGameServer || !*szGameServer ) &&
( !szSystemLock || !*szSystemLock ) )
return;
// Also don't run on the client when there's a host
char const *szSessionType = pMatchSession->GetSessionSystemData()->GetString( "type", NULL );
if ( szSessionType && !Q_stricmp( szSessionType, "client" ) )
return;
if ( !Q_stricmp( "round_start", szGameEvent ) )
{
KeyValues *kvUpdate = KeyValues::FromString(
"update",
" update { "
" game { "
" state game "
" } "
" } "
);
KeyValues::AutoDelete autodelete( kvUpdate );
pMatchSession->UpdateSessionSettings( kvUpdate );
}
else if ( !Q_stricmp( "round_end", szGameEvent ) )
{
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnProfilesWriteOpportunity", "reason", "checkpoint"
) );
}
else if ( !Q_stricmp( "finale_start", szGameEvent ) )
{
pMatchSession->UpdateSessionSettings( KeyValues::AutoDeleteInline( KeyValues::FromString(
"update",
" update { "
" game { "
" state finale "
" } "
" } "
) ) );
}
else if ( !Q_stricmp( "game_newmap", szGameEvent ) )
{
const char *szMapName = pIGameEvent->GetString( "mapname", "" );
KeyValues *kvUpdate = KeyValues::FromString(
"update",
" update { "
" game { "
" state game "
" } "
" } "
);
KeyValues::AutoDelete autodelete( kvUpdate );
Assert( szMapName && *szMapName );
if ( szMapName && *szMapName )
{
kvUpdate->SetString( "update/game/map", szMapName );
char const *szWorkshopMap = g_pMatchExtensions->GetIVEngineClient()->GetLevelNameShort();
if ( StringHasPrefix( szWorkshopMap, "workshop" ) )
{
size_t nLenMapName = Q_strlen( szMapName );
size_t nShortMapName = Q_strlen( szWorkshopMap );
if ( ( nShortMapName >= nLenMapName ) &&
!Q_stricmp( szWorkshopMap + nShortMapName - nLenMapName, szMapName ) )
// Use the name of the workshop map
kvUpdate->SetString( "update/game/map", szWorkshopMap );
}
}
pMatchSession->UpdateSessionSettings( kvUpdate );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnProfilesWriteOpportunity", "reason", "checkpoint"
) );
}
else if ( !Q_stricmp( "server_pre_shutdown", szGameEvent ) )
{
char const *szReason = pIGameEvent->GetString( "reason", "quit" );
if ( !Q_stricmp( szReason, "quit" ) )
{
DevMsg( "Received server_pre_shutdown notification - server is shutting down...\n" );
// Transform the server shutdown event into game end event
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnEngineDisconnectReason", "reason", "Server shutting down"
) );
}
}
}

View File

@@ -0,0 +1,89 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_TITLE_H
#define MM_TITLE_H
#ifdef _WIN32
#pragma once
#endif
#include "../mm_framework.h"
class CMatchTitle :
public IMatchTitle,
public IMatchEventsSink,
public IGameEventListener2
{
// Methods exposed for the framework
public:
virtual InitReturnVal_t Init();
virtual void Shutdown();
// Methods of IMatchTitle
public:
// Title ID
virtual uint64 GetTitleID();
// Service ID for XLSP
virtual uint64 GetTitleServiceID();
// Describe title settings using a bitwise combination of flags
virtual uint64 GetTitleSettingsFlags();
// Prepare network startup params for the title
virtual void PrepareNetStartupParams( void *pNetStartupParams );
// Get total number of players supported by the title
virtual int GetTotalNumPlayersSupported();
// Get a guest player name
virtual char const * GetGuestPlayerName( int iUserIndex );
// Decipher title data fields
virtual TitleDataFieldsDescription_t const * DescribeTitleDataStorage();
// Title achievements
virtual TitleAchievementsDescription_t const * DescribeTitleAchievements();
// Title avatar awards
virtual TitleAvatarAwardsDescription_t const * DescribeTitleAvatarAwards();
// Title DLC description
virtual TitleDlcDescription_t const * DescribeTitleDlcs();
// Run every frame
virtual void RunFrame() {}
// Title leaderboards
virtual KeyValues * DescribeTitleLeaderboard( char const *szLeaderboardView );
// Sets up all necessary client-side convars and user info before
// connecting to server
virtual void PrepareClientForConnect( KeyValues *pSettings );
// Start up a listen server with the given settings
virtual bool StartServerMap( KeyValues *pSettings );
// Methods of IMatchEventsSink
public:
virtual void OnEvent( KeyValues *pEvent );
// Methods of IGameEventListener2
public:
// FireEvent is called by EventManager if event just occured
// KeyValue memory will be freed by manager if not needed anymore
virtual void FireGameEvent( IGameEvent *event );
virtual int GetEventDebugID( void );
public:
CMatchTitle();
~CMatchTitle();
};
// Match title singleton
extern CMatchTitle *g_pMatchTitle;
#endif // MM_TITLE_H

View File

@@ -0,0 +1,96 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef _MM_TITLE_CONTEXTVALUES_H
#define _MM_TITLE_CONTEXTVALUES_H
#pragma once
#include "csgo.spa.h"
static ContextValue_t g_MapGroupContexts[] =
{
{ "mg_bomb", CONTEXT_CSS_MAP_GROUP_BOMB_MG },
{ "mg_hostage", CONTEXT_CSS_MAP_GROUP_HOSTAGE_MG },
{ "mg_demolition", CONTEXT_CSS_MAP_GROUP_DEMOLITION_MG },
{ "mg_armsrace", CONTEXT_CSS_MAP_GROUP_ARMSRACE_MG },
{ "mg_de_aztec", CONTEXT_CSS_MAP_GROUP_AZTEC_MG },
{ "mg_ar_baggage", CONTEXT_CSS_MAP_GROUP_BAGGAGE_MG },
{ "mg_de_bank", CONTEXT_CSS_MAP_GROUP_BANK_MG },
{ "mg_de_dust", CONTEXT_CSS_MAP_GROUP_DUST_MG },
{ "mg_de_dust2", CONTEXT_CSS_MAP_GROUP_DUST2_MG },
{ "mg_de_inferno", CONTEXT_CSS_MAP_GROUP_INFERNO_MG },
{ "mg_cs_italy", CONTEXT_CSS_MAP_GROUP_ITALY_MG },
{ "mg_de_lake", CONTEXT_CSS_MAP_GROUP_LAKE_MG },
{ "mg_de_nuke", CONTEXT_CSS_MAP_GROUP_NUKE_MG },
{ "mg_cs_office", CONTEXT_CSS_MAP_GROUP_OFFICE_MG },
{ "mg_de_safehouse", CONTEXT_CSS_MAP_GROUP_SAFEHOUSE_MG },
{ "mg_ar_shoots", CONTEXT_CSS_MAP_GROUP_SHOOTS_MG },
{ "mg_de_shorttrain", CONTEXT_CSS_MAP_GROUP_SHORTTRAIN_MG },
{ "mg_de_stmarc", CONTEXT_CSS_MAP_GROUP_STMARC_MG },
{ "mg_de_sugarcane", CONTEXT_CSS_MAP_GROUP_SUGARCANE_MG },
{ "mg_de_train", CONTEXT_CSS_MAP_GROUP_TRAIN_MG },
{ "mg_training", CONTEXT_CSS_MAP_GROUP_TRAINING_MG },
{ NULL, 0xFFFF },
};
static ContextValue_t g_LevelContexts[] =
{
{ "cs_italy", CONTEXT_CSS_LEVEL_ITALY },
{ "cs_office", CONTEXT_CSS_LEVEL_OFFICE },
{ "de_aztec", CONTEXT_CSS_LEVEL_AZTEC },
{ "de_dust", CONTEXT_CSS_LEVEL_DUST },
{ "de_dust2", CONTEXT_CSS_LEVEL_DUST2 },
{ "de_inferno", CONTEXT_CSS_LEVEL_INFERNO },
{ "de_nuke", CONTEXT_CSS_LEVEL_NUKE },
{ "ar_baggage", CONTEXT_CSS_LEVEL_BAGGAGE },
{ "ar_shoots", CONTEXT_CSS_LEVEL_SHOOTS },
{ "de_lake", CONTEXT_CSS_LEVEL_LAKE },
{ "de_bank", CONTEXT_CSS_LEVEL_BANK },
{ "de_safehouse", CONTEXT_CSS_LEVEL_SAFEHOUSE },
{ "de_sugarcane", CONTEXT_CSS_LEVEL_SUGARCANE },
{ "de_stmarc", CONTEXT_CSS_LEVEL_STMARC },
{ "de_shorttrain", CONTEXT_CSS_LEVEL_SHORTTRAIN },
{ "de_train", CONTEXT_CSS_LEVEL_TRAIN },
{ "training1", CONTEXT_CSS_LEVEL_TRAINING },
{ NULL, 0xFFFF },
};
static ContextValue_t g_GameModeContexts[] =
{
{ "casual", CONTEXT_CSS_GAME_MODE_CASUAL },
{ "competitive", CONTEXT_CSS_GAME_MODE_COMPETITIVE },
{ "gungameprogressive", CONTEXT_CSS_GAME_MODE_GUNGAMEPROGRESSIVE },
{ "gungametrbomb", CONTEXT_CSS_GAME_MODE_GUNGAMEBOMB },
{ NULL, 0xFFFF },
};
static ContextValue_t g_GameModeAsNumberContexts[] =
{
{ "casual", 0 },
{ "competitive", 1 },
{ "competitive_unranked", 2 },
{ "pro", 4 },
{ "gungameprogressive", 10 },
{ "gungameselect", 20 },
{ "gungametrbomb", 30 },
{ NULL, 0xFFFF },
};
static ContextValue_t g_GameTypeContexts[] =
{
{ "classic", CONTEXT_CSS_GAME_TYPE_CLASSIC },
{ "gungame", CONTEXT_CSS_GAME_TYPE_GUNGAME },
{ NULL, 0xFFFF },
};
static ContextValue_t g_PrivacyContexts[] =
{
{ "public", CONTEXT_CSS_PRIVACY_PUBLIC },
{ "private", CONTEXT_CSS_PRIVACY_INVITE_ONLY },
{ "friends", CONTEXT_CSS_PRIVACY_FRIENDS },
{ NULL, 0xFFFF },
};
#endif // _MM_TITLE_CONTEXTVALUES_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "../mm_title_main.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
LINK_MATCHMAKING_LIB();
static CMatchTitle g_MatchTitle;
CMatchTitle *g_pMatchTitle = &g_MatchTitle;
IMatchTitle *g_pIMatchTitle = g_pMatchTitle;
IMatchEventsSink *g_pIMatchTitleEventsSink = g_pMatchTitle;
//
// Init / shutdown
//
InitReturnVal_t MM_Title_Init()
{
return g_pMatchTitle->Init();
}
void MM_Title_Shutdown()
{
if ( g_pMatchTitle )
g_pMatchTitle->Shutdown();
}
#if defined( _X360 ) && defined( _DEBUG )
CON_COMMAND( x_dbg_xgi, "Set X360 XGI debug output level" )
{
int iLevel = args.FindArgInt( "-level", -1 );
if ( iLevel >= 0 )
{
XDebugSetSystemOutputLevel( HXAMAPP_XGI, iLevel );
}
}
#endif

View File

@@ -0,0 +1,330 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title_richpresence.h"
#include "mm_title_contextvalues.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static void SetAllUsersContext( DWORD dwContextId, DWORD dwValue, bool bAsync = true )
{
#ifdef _X360
for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k )
{
if ( XBX_GetUserIsGuest( k ) )
continue;
int iCtrlr = XBX_GetUserId( k );
//if ( dwContextId == X_CONTEXT_PRESENCE ) DevMsg( "Set presence to %d for user %d\n", dwValue, iCtrlr );
if ( bAsync )
XUserSetContextEx( iCtrlr, dwContextId, dwValue, MMX360_NewOverlappedDormant() );
else
XUserSetContext( iCtrlr, dwContextId, dwValue );
}
#endif
}
static void SetAllUsersProperty( DWORD dwPropertyId, DWORD cbValue, void const *pvValue )
{
#ifdef _X360
for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k )
{
if ( XBX_GetUserIsGuest( k ) )
continue;
int iCtrlr = XBX_GetUserId( k );
XUserSetPropertyEx( iCtrlr, dwPropertyId, cbValue, pvValue, MMX360_NewOverlappedDormant() );
}
#endif
}
KeyValues * MM_Title_RichPresence_PrepareForSessionCreate( KeyValues *pSettings )
{
SetAllUsersContext( X_CONTEXT_GAME_MODE, CONTEXT_GAME_MODE_CSS_GAME_MODE_MULTIPLAYER, false );
// matchmaking version
{
static int val; // must be valid for the async call
extern ConVar mm_title_debug_version;
val = mm_title_debug_version.GetInt();
SetAllUsersProperty( PROPERTY_CSS_MATCH_VERSION, sizeof( val ), &val );
DevMsg( "PrepareForSessionCreate: matchmaking version %d\n", val );
}
return NULL;
}
void MM_Title_RichPresence_Update( KeyValues *pFullSettings, KeyValues *pUpdatedSettings )
{
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
( void ) g_pMatchExtensions->GetIBaseClientDLL()->GetRichPresenceStatusString();
#endif
#ifdef _X360
if ( !pFullSettings )
{
SetAllUsersContext( X_CONTEXT_PRESENCE, CONTEXT_PRESENCE_MAINMENU ); // main menu
return;
}
// Also set players information during initial rich presence update
if ( !pUpdatedSettings && pFullSettings )
{
MM_Title_RichPresence_PlayersChanged( pFullSettings );
// Open slots
int numSlots = pFullSettings->GetInt( "members/numSlots", XBX_GetNumGameUsers() );
{
static int val; // must be valid for the async call
val = numSlots;
SetAllUsersProperty( PROPERTY_CSS_OPEN_SLOTS, sizeof( val ), &val );
}
// Team slots
int numTeamSlots = MAX( pFullSettings->GetInt( "members/numTSlotsFree", 0 ), pFullSettings->GetInt( "members/numCTSlotsFree", 0 ) );
{
static int val; // must be valid for the async call
val = numTeamSlots;
SetAllUsersProperty( PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS, sizeof( val ), &val );
}
// Skill fields
{
static int val = 0; // must be valid for the async call
SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_EXPERIENCE, sizeof( val ), &val );
SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL0, sizeof( val ), &val );
SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL1, sizeof( val ), &val );
SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL2, sizeof( val ), &val );
SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL3, sizeof( val ), &val );
SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL4, sizeof( val ), &val );
}
// Listen/dedicated server resolver
{
static int val; // must be valid for the async call
val = 0;
extern ConVar mm_title_debug_dccheck;
if ( mm_title_debug_dccheck.GetInt() )
val = ( ( mm_title_debug_dccheck.GetInt() > 0 ) ? 0 : 1 );
SetAllUsersProperty( PROPERTY_CSS_SEARCH_LISTEN_SERVER, sizeof( val ), &val );
}
}
// pUpdatedSettings = NULL when the session is created and all contexts need to be set
KeyValues *pNewSettings = pUpdatedSettings ? pUpdatedSettings : pFullSettings;
// if ( KeyValues *kvVal = pNewSettings->FindKey( "game/dlcrequired" ) )
// {
// static int val[10]; // must be valid for the async call
// uint64 uiDlcRequired = kvVal->GetUint64();
// extern ConVar mm_matchmaking_dlcsquery;
// for ( int k = 1; k <= mm_matchmaking_dlcsquery.GetInt(); ++ k )
// {
// val[k] = !!( uiDlcRequired & ( 1ull << k ) );
// DevMsg( "DLC%d required: %d\n", k, val[k] );
// SetAllUsersProperty( PROPERTY_REQUIRED_DLC1 - 1 + k, sizeof( val ), &val );
// }
// }
// Actual game type (classic, gungame)
if ( char const *szGameType = pNewSettings->GetString( "game/type", NULL ) )
{
SetAllUsersContext( CONTEXT_CSS_GAME_TYPE, g_GameTypeContexts->ScanValues( szGameType ) );
}
// Game state
if ( char const *szGameState = pNewSettings->GetString( "game/state", NULL ) )
{
if ( !V_stricmp( pFullSettings->GetString( "system/network" ), "offline" ) )
SetAllUsersContext( CONTEXT_GAME_STATE, CONTEXT_GAME_STATE_SINGLE_PLAYER );
else
SetAllUsersContext( CONTEXT_GAME_STATE, ( !V_stricmp( "game", szGameState ) ) ? CONTEXT_GAME_STATE_MULTIPLAYER : CONTEXT_GAME_STATE_IN_MENUS );
}
// Actual game mode (casual, competitive, pro, etc)
if ( char const *szValue = pNewSettings->GetString( "game/mode", NULL ) )
{
SetAllUsersContext( CONTEXT_CSS_GAME_MODE, g_GameModeContexts->ScanValues( szValue ) );
static int val; // must be valid for the async call
val = g_GameModeAsNumberContexts->ScanValues( szValue );
SetAllUsersProperty( PROPERTY_CSS_GAME_MODE_AS_NUMBER, sizeof( val ), &val );
}
// MapGroup being used
if ( char const *szMapGroupName = pNewSettings->GetString( "game/mapgroupname", NULL ) )
{
SetAllUsersContext( CONTEXT_CSS_MAP_GROUP, g_MapGroupContexts->ScanValues( szMapGroupName ) );
}
// Privacy
if ( char const *szPrivacy = pNewSettings->GetString( "system/access", NULL ) )
{
SetAllUsersContext( CONTEXT_CSS_PRIVACY, g_PrivacyContexts->ScanValues( szPrivacy ) );
}
// Listen server
if ( char const *szListenServer = pNewSettings->GetString( "server/server", NULL ) )
{
static int val; // must be valid for the async call
val = ( !V_stricmp( "listen", szListenServer ) ? 1 : 0 );
extern ConVar mm_title_debug_dccheck;
if ( mm_title_debug_dccheck.GetInt() )
val = ( ( mm_title_debug_dccheck.GetInt() > 0 ) ? 0 : 1 );
SetAllUsersProperty( PROPERTY_CSS_SEARCH_LISTEN_SERVER, sizeof( val ), &val );
}
//
// Determine Rich Presence Display
//
if ( char const *szGameModeForRichPresence = pFullSettings->GetString( "game/mode", NULL ) )
{
// Online/Offline
if ( char const *szNetwork = pFullSettings->GetString( "system/network", NULL ) )
{
DWORD dwLevelPresence = CONTEXT_PRESENCE_MAINMENU;
if ( V_stricmp( "offline", szNetwork ) == 0 )
{
dwLevelPresence = CONTEXT_PRESENCE_SINGLEPLAYER;
}
else if ( !V_stricmp( "lobby", pFullSettings->GetString( "game/state" ) ) )
{
dwLevelPresence = CONTEXT_PRESENCE_LOBBY;
}
else
{
// Privacy
if ( char const *szPrivacy = pFullSettings->GetString( "system/access", NULL ) )
{
DWORD dwPrivacy = g_PrivacyContexts->ScanValues( szPrivacy );
if ( dwPrivacy == CONTEXT_CSS_PRIVACY_PUBLIC )
{
// Public match
// See if there are any free slots
int numSlots = pFullSettings->GetInt( "members/numSlots", 0 );
int numPlayers = pFullSettings->GetInt( "members/numPlayers", 0 );
dwLevelPresence = (numSlots > numPlayers) ? CONTEXT_PRESENCE_MULTIPLAYER : CONTEXT_PRESENCE_MULTIPLAYER_NO_SLOTS;
}
else
{
// Private/invite only match
dwLevelPresence = CONTEXT_PRESENCE_MULTIPLAYER_PRIVATE;
}
}
}
SetAllUsersContext( X_CONTEXT_PRESENCE, dwLevelPresence );
}
// Update the map being used
DWORD dwMapRichPresence = pFullSettings->GetInt( "game/mapRichPresence", 0xFFFF );
if ( dwMapRichPresence == 0xFFFF )
{
// We didn't have a richpresence context set in GameModes.txt so look it up based on the name
if ( char const *szMapName = pFullSettings->GetString( "game/map", NULL ) )
{
dwMapRichPresence = g_LevelContexts->ScanValues( szMapName );
}
}
if ( dwMapRichPresence != 0xFFFF )
{
SetAllUsersContext( CONTEXT_CSS_LEVEL, dwMapRichPresence );
}
}
#endif // _X360
}
void MM_Title_RichPresence_PlayersChanged( KeyValues *pFullSettings )
{
//#ifdef _X360
// // Set the installed DLCs masks
// static int val[10]; // must be valid for the async call
// uint64 uiDlcInstalled = g_pMatchFramework->GetMatchSystem()->GetDlcManager()->GetDataInfo()->GetUint64( "@info/installed" );
// extern ConVar mm_matchmaking_dlcsquery;
// for ( int k = 1; k <= mm_matchmaking_dlcsquery.GetInt(); ++ k )
// {
// val[k] = !!( uiDlcInstalled & ( 1ull << k ) );
// DevMsg( "DLC%d installed: %d\n", k, val[k] );
// SetAllUsersProperty( PROPERTY_INSTALLED_DLC1 - 1 + k, sizeof( val[k] ), &val[k] );
// }
//#endif
}
// Called by the client to notify matchmaking that it should update matchmaking properties based
// on player distribution among the teams.
void MM_Title_RichPresence_UpdateTeamPropertiesCSGO( KeyValues *pCurrentSettings, KeyValues *pTeamProperties )
{
#ifdef _X360
int numSlots = pCurrentSettings->GetInt( "members/numSlots", 0 );
int numPlayers = pTeamProperties->GetInt( "members/numPlayers", 0 );
int numSpectators = pTeamProperties->GetInt( "members/numSpectators", 0 );
int numExtraSpectatorSlots = pCurrentSettings->GetInt( "members/numExtraSpectatorSlots", 0 );
int numFreeTSlots = pCurrentSettings->GetInt( "members/numTSlotsFree", 0 );
int numFreeCTSlots = pCurrentSettings->GetInt( "members/numCTSlotsFree", 0 );
// Spectator overflow is computed in case we end up in a situation in which there are more
// spectators than spectator slots. In that case, the extra spectators are counted against
// the active player slots.
int spectatorOverflow = numSpectators - numExtraSpectatorSlots;
static int nPROPERTY_CSS_OPEN_SLOTS;
nPROPERTY_CSS_OPEN_SLOTS = numSlots - numPlayers;
if ( spectatorOverflow > 0 )
{
nPROPERTY_CSS_OPEN_SLOTS = numSlots - ( numPlayers - numExtraSpectatorSlots + spectatorOverflow );
}
SetAllUsersProperty( PROPERTY_CSS_OPEN_SLOTS, sizeof( nPROPERTY_CSS_OPEN_SLOTS ), &nPROPERTY_CSS_OPEN_SLOTS );
// post the average skill rank so matchmaking will work
int avgRank = pTeamProperties->GetInt( "members/timeout", DEFAULT_NEW_PLAYER_ELO_RANK );
static int nPROPERTY_CSS_AGGREGATE_SKILL0;
nPROPERTY_CSS_AGGREGATE_SKILL0 = avgRank;
SetAllUsersProperty( PROPERTY_CSS_AGGREGATE_SKILL0, sizeof( nPROPERTY_CSS_AGGREGATE_SKILL0 ), &nPROPERTY_CSS_AGGREGATE_SKILL0 );
// Post the maximum number of free slots on either team, for team matchmaking only
static int nPROPERTY_CSS_MAX_OPEN_TEAM_SLOTS;
nPROPERTY_CSS_MAX_OPEN_TEAM_SLOTS = max( numFreeTSlots, numFreeCTSlots );
SetAllUsersProperty( PROPERTY_CSS_MAX_OPEN_TEAM_SLOTS, sizeof( nPROPERTY_CSS_MAX_OPEN_TEAM_SLOTS ), &nPROPERTY_CSS_MAX_OPEN_TEAM_SLOTS );
#elif defined( _PS3 )
// This is hacky: we stuff the rank into system data so that if lobby migrates
// to a new owner and the metadata wasn't correctly set by the previous owner
// then the new owner will set it
if ( IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession() )
{
KeyValues *kvSystemData = pMatchSession->GetSessionSystemData();
if ( !kvSystemData )
return;
int avgRank = pTeamProperties->GetInt( "members/timeout", DEFAULT_NEW_PLAYER_ELO_RANK );
kvSystemData->SetInt( "timeout", avgRank );
int numOpenSlots = 10 - pTeamProperties->GetInt( "members/numPlayers" );
if ( numOpenSlots < 0 )
numOpenSlots = 0;
kvSystemData->SetInt( "numOpenSlots", numOpenSlots );
char const *szSessionType = kvSystemData->GetString( "type", NULL );
DevMsg( "Session timeout value=%d (%s)\n", avgRank, szSessionType ? szSessionType : "offline" );
DevMsg( "Session numOpenSlots=%d (%s)\n", numOpenSlots, szSessionType ? szSessionType : "offline" );
if ( szSessionType && !Q_stricmp( szSessionType, "client" ) )
return; // don't run on clients for now, maybe later when we become owner
steamapicontext->SteamMatchmaking()->SetLobbyData( kvSystemData->GetUint64( "xuidReserve", 0ull ), "game:timeout", CFmtStr( "%u", avgRank ) );
steamapicontext->SteamMatchmaking()->SetLobbyData( kvSystemData->GetUint64( "xuidReserve", 0ull ), "game:numOpenSlots", CFmtStr( "%u", numOpenSlots ) );
}
#endif
}

View File

@@ -0,0 +1,21 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_TITLE_RICHPRESENCE_H
#define MM_TITLE_RICHPRESENCE_H
#ifdef _WIN32
#pragma once
#endif
#include "mm_title.h"
void MM_Title_RichPresence_Update( KeyValues *pFullSettings, KeyValues *pUpdatedSettings );
void MM_Title_RichPresence_UpdateTeamPropertiesCSGO( KeyValues *pCurrentSettings, KeyValues *pTeamProperties );
void MM_Title_RichPresence_PlayersChanged( KeyValues *pFullSettings );
KeyValues * MM_Title_RichPresence_PrepareForSessionCreate( KeyValues *pSettings );
#endif // MM_TITLE_H

View File

@@ -0,0 +1,557 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "matchmaking/cstrike15/imatchext_cstrike15.h"
#include "inputsystem/iinputsystem.h"
#include "platforminputdevice.h"
#include "netmessages_signon.h"
#ifndef NO_STEAM
#include "steam/isteamuserstats.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static ConVar cl_titledataversionblock1( "cl_titledataversionblock1", "14", FCVAR_DEVELOPMENTONLY, "stats for console title data block1 i/o version." );
static ConVar cl_titledataversionblock2( "cl_titledataversionblock2", "8", FCVAR_DEVELOPMENTONLY, "stats for console title data block2 i/o version." );
static ConVar cl_titledataversionblock3( "cl_titledataversionblock3", "48", FCVAR_DEVELOPMENTONLY, "stats for console title data block3 i/o version." );
static TitleDataFieldsDescription_t const * PrepareTitleDataStorageDescription()
{
#if defined( _X360 )
#define TD_ENTRY( szName, nTD, eDataType, numBytesOffset ) \
{ \
TitleDataFieldsDescription_t aTDFD = { szName, TitleDataFieldsDescription_t::nTD, TitleDataFieldsDescription_t::eDataType, numBytesOffset }; \
s_tdfd.AddToTail( aTDFD ); \
if ( numBytesOffset >= XPROFILE_SETTING_MAX_SIZE ) \
Warning( "\nnumBytesOffset %d is > XPROFILE_SETTING_MAX_SIZE in TD_ENTRY for %s\n\n", numBytesOffset, szName ); \
}
#else
#define TD_ENTRY( szName, nTD, eDataType, numBytesOffset ) \
{ \
TitleDataFieldsDescription_t aTDFD = { szName, TitleDataFieldsDescription_t::nTD, TitleDataFieldsDescription_t::eDataType, numBytesOffset }; \
s_tdfd.AddToTail( aTDFD ); \
}
#endif // _X360
static CUtlVector< TitleDataFieldsDescription_t > s_tdfd;
#if defined( _X360 )
// versioning info for the title data blocks
char *pTitleDataBlock[3];
pTitleDataBlock[0] = new char[30];
pTitleDataBlock[1] = new char[30];
pTitleDataBlock[2] = new char[30];
Q_snprintf( pTitleDataBlock[0], 30, "TITLEDATA.BLOCK1.VERSION" );
Q_snprintf( pTitleDataBlock[1], 30, "TITLEDATA.BLOCK2.VERSION" );
Q_snprintf( pTitleDataBlock[2], 30, "TITLEDATA.BLOCK3.VERSION" );
TD_ENTRY( pTitleDataBlock[0], DB_TD1, DT_uint16, offsetof( TitleData1, versionNumber ) );
TD_ENTRY( pTitleDataBlock[1], DB_TD2, DT_uint16, offsetof( TitleData2, versionNumber ) );
TD_ENTRY( pTitleDataBlock[2], DB_TD3, DT_uint16, offsetof( TitleData3, versionNumber ) );
// stats
#define CFG( cppType, name ) \
TD_ENTRY( "STATS.usr." #name, DB_TD1, DT_##cppType, offsetof( TitleData1, usrStats.name ) )
#include "xlast_csgo/inc_stats_usr.inc"
#undef CFG
// loadouts
//we are using an array so we can pack this info as tight as possible to fit into the 1K block
#define CFG( loadoutnum, equipmentnum ) \
int numLoadouts = loadoutnum; \
int numEquipmentSlots = equipmentnum;
#include "xlast_csgo/inc_loadouts_usr.inc"
#undef CFG
int loadoutDataIndex = 0;
for ( int team=0; team<2; ++team )
{
char teamName[10];
if ( team == 0 )
Q_snprintf( teamName, 10, "CT" );
else
Q_snprintf( teamName, 10, "T" );
for (int i=0; i<numLoadouts; ++i)
{
for (int j=0; j<numEquipmentSlots; ++j)
{
char *loadoutName = new char[30];
Q_snprintf( loadoutName, 30, "%s.LOAD%.1d.EQUIP%.1d.ID", teamName, i, j );
TD_ENTRY( loadoutName, DB_TD3, DT_uint8, offsetof( TitleData3, loadoutData[ loadoutDataIndex++ ] ) );
loadoutName = new char[30];
Q_snprintf( loadoutName, 30, "%s.LOAD%.1d.EQUIP%.1d.QUANTITY", teamName, i, j );
TD_ENTRY( loadoutName, DB_TD3, DT_uint8, offsetof( TitleData3, loadoutData[ loadoutDataIndex++ ] ) );
}
char *loadoutName = new char[30];
Q_snprintf( loadoutName, 30, "%s.LOAD%.1d.PRIMARY", teamName, i );
TD_ENTRY( loadoutName, DB_TD3, DT_uint8, offsetof( TitleData3, loadoutData[ loadoutDataIndex++ ] ) );
loadoutName = new char[30];
Q_snprintf( loadoutName, 30, "%s.LOAD%.1d.SECONDARY", teamName, i );
TD_ENTRY( loadoutName, DB_TD3, DT_uint8, offsetof( TitleData3, loadoutData[ loadoutDataIndex++ ] ) );
loadoutName = new char[30];
Q_snprintf( loadoutName, 30, "%s.LOAD%.1d.FLAGS", teamName, i );
TD_ENTRY( loadoutName, DB_TD3, DT_uint8, offsetof( TitleData3, loadoutData[ loadoutDataIndex++ ] ) );
}
}
// medals
#define CFG( buffer ) \
for ( int i=0; i< buffer; ++i ) \
{ \
char *pAwardedName = new char[ 20 ]; \
Q_snprintf( pAwardedName, 20, "MEDALS.AWARDED%.3d", i ); \
TD_ENTRY( pAwardedName, DB_TD2, DT_uint8, offsetof( TitleData2, CSMedalsAwarded[i] ) ) \
char *pMedalInfoName = new char[ 25 ]; \
Q_snprintf( pMedalInfoName, 25, "MEDALS.MEDALINFO%.3d", i ); \
TD_ENTRY( pMedalInfoName, DB_TD2, DT_uint32, offsetof( TitleData2, CSMedalsMedalInfo[i] ) ) \
}
#include "xlast_csgo/inc_medals_usr.inc"
#undef CFG
#endif // _X360
#if defined( _GAMECONSOLE )
// system convars
#define CFG( name, scfgType, cppType ) \
TD_ENTRY( TITLE_DATA_PREFIX "CFG.sys." #name, DB_TD3, DT_##cppType, offsetof( TitleData3, cvSystem.name ) )
#include "xlast_csgo/inc_gameconsole_settings_sys.inc"
#undef CFG
// profile-specific convars
#define CFG( name, scfgType, cppType ) \
TD_ENTRY( TITLE_DATA_PREFIX "CFG.usr." #name, DB_TD3, DT_##cppType, offsetof( TitleData3, cvUser.name ) ) \
TD_ENTRY( TITLE_DATA_PREFIX "CFG.usrSS." #name, DB_TD3, DT_##cppType, offsetof( TitleData3, cvUserSS.name ) )
#include "xlast_csgo/inc_gameconsole_settings_usr.inc"
#include "xlast_csgo/inc_gameconsole_device_specific_settings_usr.inc"
#undef CFG
// joystick bindings
#define ACTION( name )
#define BINDING( name, cppType ) \
TD_ENTRY( TITLE_DATA_PREFIX "BINDING." #name, DB_TD3, DT_##cppType, offsetof( TitleData3, JoystickBindings.name ) )
#include "xlast_csgo/inc_bindings_usr.inc"
#undef BINDING
#if defined( _PS3 )
// PS3 also has keyboard bindings.
#define BINDING( name, cppType ) \
TD_ENTRY( TITLE_DATA_PREFIX "BINDING." #name, DB_TD3, DT_##cppType, offsetof( TitleData3, JoystickBindings.name ) )
#include "xlast_csgo/inc_ps3_key_bindings_usr.inc"
#undef BINDING
// For PS3 we have two additional sets of button bindings one for Sharp Shooter, the other for Move.
// We also have a few device specific convar settings.
// PS Move specific bindings and settings
#define BINDING( name, cppType ) \
TD_ENTRY( TITLE_DATA_PREFIX TITLE_DATA_DEVICE_MOVE_PREFIX "BINDING." #name, DB_TD3, DT_##cppType, offsetof( TitleData3, JoystickBindings.PSMove.name ) )
#include "xlast_csgo/inc_bindings_usr.inc"
#undef BINDING
#define CFG( name, scfgType, cppType ) \
TD_ENTRY( TITLE_DATA_PREFIX TITLE_DATA_DEVICE_MOVE_PREFIX "CFG.usr." #name, DB_TD3, DT_##cppType, offsetof( TitleData3, cvUser.PSMove.name ) )
#include "xlast_csgo/inc_gameconsole_device_specific_settings_usr.inc"
#undef CFG
// Sharp Shooter specific bindings and settings
#define BINDING( name, cppType ) \
TD_ENTRY( TITLE_DATA_PREFIX TITLE_DATA_DEVICE_SHARP_SHOOTER_PREFIX "BINDING." #name, DB_TD3, DT_##cppType, offsetof( TitleData3, JoystickBindings.SharpShooter.name ) )
#include "xlast_csgo/inc_bindings_usr.inc"
#undef BINDING
#define CFG( name, scfgType, cppType ) \
TD_ENTRY( TITLE_DATA_PREFIX TITLE_DATA_DEVICE_SHARP_SHOOTER_PREFIX "CFG.usr." #name, DB_TD3, DT_##cppType, offsetof( TitleData3, cvUser.SharpShooter.name ) )
#include "xlast_csgo/inc_gameconsole_device_specific_settings_usr.inc"
#undef CFG
#endif
#undef ACTION
// Player Rankings by mode, controller, w/ optional history
int rankIndex = 0;
int numControllers = PlatformInputDevice::GetInputDeviceCountforPlatform();
for ( int m = 0; m < ELOTitleData::NUM_GAME_MODES_ELO_RANKED; m++ )
{
for ( int c = 1; c <= numControllers; c++ )
{
char *pRankingName = new char[ 30 ];
V_snprintf( pRankingName, 30, TITLE_DATA_PREFIX "ELO.MODE%d.CTR%d", m, c );
TD_ENTRY( pRankingName, DB_TD3, DT_ELO, offsetof( TitleData3, playerRankingsData[ rankIndex ] ) );
rankIndex++;
}
// Record the bracket and some info for calculating it. Only legal controllers are game console controllers.
char *pBracketInfoName = new char[ 30 ];
V_snprintf( pBracketInfoName, 30, TITLE_DATA_PREFIX"ELO.MODE%d.BRACKETINFO", m );
TD_ENTRY( pBracketInfoName, DB_TD3, DT_uint16, offsetof( TitleData3, EloBracketInfo[ m ] ) );
}
#endif // _GAMECONSOLE
#if defined ( _X360 )
// matchmaking data
#define CFG( cppType, name ) \
TD_ENTRY( "MMDATA.usr." #name, DB_TD3, DT_##cppType, offsetof( TitleData3, usrMMData.name ) )
#include "xlast_csgo/inc_mmdata_usr.inc"
#undef CFG
#endif // #if defined ( _X360 )
// END MARKER
TD_ENTRY( (const char*) NULL, DB_TD3, DT_0, 0 )
#undef TD_ENTRY
#if defined( _X360 )
COMPILE_TIME_ASSERT( sizeof( TitleData1 ) < XPROFILE_SETTING_MAX_SIZE );
COMPILE_TIME_ASSERT( sizeof( TitleData2 ) < XPROFILE_SETTING_MAX_SIZE );
COMPILE_TIME_ASSERT( sizeof( TitleData3 ) < XPROFILE_SETTING_MAX_SIZE );
#endif
return s_tdfd.Base();
}
TitleDataFieldsDescription_t const * CMatchTitle::DescribeTitleDataStorage()
{
static TitleDataFieldsDescription_t const *s_pTDFD = PrepareTitleDataStorageDescription();
return s_pTDFD;
}
TitleAchievementsDescription_t const * CMatchTitle::DescribeTitleAchievements()
{
static TitleAchievementsDescription_t tad[] =
{
//#include "left4dead2.xhelp.achtitledesc.txt"
// END MARKER
{ NULL, 0 }
};
return tad;
}
TitleAvatarAwardsDescription_t const * CMatchTitle::DescribeTitleAvatarAwards()
{
static TitleAvatarAwardsDescription_t taad[] =
{
//#include "left4dead2.xhelp.avawtitledesc.txt"
// END MARKER
{ NULL, 0 }
};
return taad;
}
TitleDlcDescription_t const * CMatchTitle::DescribeTitleDlcs()
{
static TitleDlcDescription_t tdlcs[] =
{
//{ PORTAL2_DLCID_COOP_BOT_SKINS, PORTAL2_DLC_APPID_COOP_BOT_SKINS, PORTAL2_DLC_PKGID_COOP_BOT_SKINS, "DLC.0x12" },
//{ PORTAL2_DLCID_COOP_BOT_HELMETS, PORTAL2_DLC_APPID_COOP_BOT_HELMETS, PORTAL2_DLC_PKGID_COOP_BOT_HELMETS, "DLC.0x13" },
//{ PORTAL2_DLCID_COOP_BOT_ANTENNA, PORTAL2_DLC_APPID_COOP_BOT_ANTENNA, PORTAL2_DLC_PKGID_COOP_BOT_ANTENNA, "DLC.0x14" },
// END MARKER
{ 0, 0, 0 }
};
return tdlcs;
}
// Title leaderboards
KeyValues * CMatchTitle::DescribeTitleLeaderboard( char const *szLeaderboardView )
{
#if !defined( NO_STEAM )
if ( StringAfterPrefix( szLeaderboardView, "WINS_" ) )
{
if ( IsPC() || IsPS3() )
{
KeyValues *pSettings = KeyValues::FromString( "SteamLeaderboard",
" :score wins_ratio " // :score is the leaderboard value mapped to game name "besttime"
" :payloadformat { " // This describes the payload format.
" payload0 { "
" :score total_wins"
" :format int "
" :upload sum "
" } "
" payload1 { "
" :score total_losses "
" :format int "
" :upload sum "
" } "
" payload2 { "
" :score win_as_ct "
" :format int "
" :upload sum "
" } "
" payload3 { "
" :score win_as_t "
" :format int "
" :upload sum "
" } "
" payload4 { "
" :score loss_as_ct "
" :format int "
" :upload sum "
" } "
" payload5 { "
" :score loss_as_t "
" :format int "
" :upload sum "
" } "
" } "
);
pSettings->SetString( ":scoreformula", "( payload0 / max( payload0 + payload1, 1 ) ) * ( min( payload0 + payload1, 20 ) / 20 ) * 10000000" );
pSettings->SetInt( ":sort", k_ELeaderboardSortMethodDescending ); // Sort order when fetching and displaying leaderboard data
pSettings->SetInt( ":format", k_ELeaderboardDisplayTypeNumeric ); // Note: this is actually 1/100th seconds type, Steam change pending
pSettings->SetInt( ":upload", k_ELeaderboardUploadScoreMethodForceUpdate ); // Upload method when writing to leaderboard
return pSettings;
}
}
else if ( StringAfterPrefix( szLeaderboardView, "CS_" ) )
{
if ( IsPC() || IsPS3() )
{
KeyValues *pSettings = KeyValues::FromString( "SteamLeaderboard",
" :score average_contribution " // :score is the leaderboard value mapped to game name "besttime"
" :payloadformat { " // This describes the payload format.
" payload0 { "
" :score mvp_awards"
" :format int "
" :upload sum "
" } "
" payload1 { "
" :score rounds_played "
" :format int "
" :upload sum "
" } "
" payload2 { "
" :score total_contribution "
" :format int "
" :upload sum "
" } "
" payload3 { "
" :score kills "
" :format int "
" :upload sum "
" } "
" payload4 { "
" :score deaths "
" :format int "
" :upload sum "
" } "
" payload5 { "
" :score damage "
" :format int "
" :upload sum "
" } "
" } "
);
pSettings->SetString( ":scoreformula", "( payload2 / max( payload1, 1 ) )" );
pSettings->SetInt( ":sort", k_ELeaderboardSortMethodDescending ); // Sort order when fetching and displaying leaderboard data
pSettings->SetInt( ":format", k_ELeaderboardDisplayTypeNumeric ); // Note: this is actually 1/100th seconds type, Steam change pending
pSettings->SetInt( ":upload", k_ELeaderboardUploadScoreMethodForceUpdate ); // Upload method when writing to leaderboard
return pSettings;
}
}
else if ( StringAfterPrefix( szLeaderboardView, "KD_" ) )
{
if ( IsPC() || IsPS3() )
{
KeyValues *pSettings = KeyValues::FromString( "SteamLeaderboard",
" :score kd_ratio " // :score is the leaderboard value mapped to game name "besttime"
" :payloadformat { " // This describes the payload format.
" payload0 { "
" :score kills"
" :format int "
" :upload sum "
" } "
" payload1 { "
" :score deaths "
" :format int "
" :upload sum "
" } "
" payload2 { "
" :score rounds_played "
" :format int "
" :upload sum "
" } "
" payload3 { "
" :score shots_fired "
" :format int "
" :upload sum "
" } "
" payload4 { "
" :score head_shots "
" :format int "
" :upload sum "
" } "
" payload5 { "
" :score shots_hit "
" :format int "
" :upload sum "
" } "
" } "
);
pSettings->SetString( ":scoreformula", "( payload0 / max( payload1, 1 ) ) * ( min( payload2, 20 ) / 20 ) * 10000000" );
pSettings->SetInt( ":sort", k_ELeaderboardSortMethodDescending ); // Sort order when fetching and displaying leaderboard data
pSettings->SetInt( ":format", k_ELeaderboardDisplayTypeNumeric ); // Note: this is actually 1/100th seconds type, Steam change pending
pSettings->SetInt( ":upload", k_ELeaderboardUploadScoreMethodForceUpdate ); // Upload method when writing to leaderboard
return pSettings;
}
}
else if ( StringAfterPrefix( szLeaderboardView, "STARS_" ) )
{
if ( IsPC() || IsPS3() )
{
KeyValues *pSettings = KeyValues::FromString( "SteamLeaderboard",
" :score numstars " // :score is the leaderboard value mapped to game name "besttime"
" :scoresum 1 "
" :payloadformat { " // This describes the payload format.
" payload0 { "
" :score bombs_planted "
" :format int "
" :upload sum "
" } "
" payload1 { "
" :score bombs_detonated "
" :format int "
" :upload sum "
" } "
" payload2 { "
" :score bombs_defused "
" :format int "
" :upload sum "
" } "
" payload3 { "
" :score hostages_rescued "
" :format int "
" :upload sum "
" } "
" } "
);
pSettings->SetInt( ":sort", k_ELeaderboardSortMethodDescending ); // Sort order when fetching and displaying leaderboard data
pSettings->SetInt( ":format", k_ELeaderboardDisplayTypeNumeric ); // Note: this is actually 1/100th seconds type, Steam change pending
pSettings->SetInt( ":upload", k_ELeaderboardUploadScoreMethodKeepBest ); // Upload method when writing to leaderboard
return pSettings;
}
}
else if ( StringAfterPrefix( szLeaderboardView, "GP_" ) )
{
if ( IsPC() || IsPS3() )
{
KeyValues *pSettings = KeyValues::FromString( "SteamLeaderboard",
" :score num_rounds " // :score is the leaderboard value mapped to game name "besttime"
" :scoresum 1 "
" :payloadformat { " // This describes the payload format.
" payload0 { "
" :score time_played "
" :format uint64 "
" :upload sum "
" } "
" payload1 { "
" :score time_played_ct "
" :format uint64 "
" :upload sum "
" } "
" payload2 { "
" :score time_played_t "
" :format uint64 "
" :upload sum "
" } "
" payload3 { "
" :score total_medals "
" :format int "
" :upload last " // the last value written is the authoritative value of total achievement medals unlocked
" } "
" } "
);
pSettings->SetInt( ":sort", k_ELeaderboardSortMethodDescending ); // Sort order when fetching and displaying leaderboard data
pSettings->SetInt( ":format", k_ELeaderboardDisplayTypeNumeric ); // Note: this is actually 1/100th seconds type, Steam change pending
pSettings->SetInt( ":upload", k_ELeaderboardUploadScoreMethodKeepBest ); // Upload method when writing to leaderboard
return pSettings;
}
}
#endif // !NO_STEAM
/*
// Check if this is a survival leaderboard
if ( char const *szSurvivalMap = StringAfterPrefix( szLeaderboardView, "survival_" ) )
{
if ( IsX360() )
{
// Find the corresponding record in the mission script
KeyValues *pSettings = new KeyValues( "settings" );
KeyValues::AutoDelete autodelete_pSettings( pSettings );
pSettings->SetString( "game/mode", "survival" );
KeyValues *pMissionInfo = NULL;
KeyValues *pMapInfo = g_pMatchExtL4D->GetMapInfoByBspName( pSettings, szSurvivalMap, &pMissionInfo );
if ( !pMapInfo || !pMissionInfo )
return NULL;
// Find the leaderboard description in the map info
KeyValues *pLbDesc = pMapInfo->FindKey( "x360leaderboard" );
if ( !pLbDesc )
return NULL;
// Insert the required keys
pLbDesc = pLbDesc->MakeCopy();
static KeyValues *s_pRatingKey = KeyValues::FromString( ":rating", // X360 leaderboards are rated
" name besttime " // game name of the rating field is "besttime"
" type uint64 " // type is uint64
" rule max" // rated field must be greater than cached value so that it can be written
);
pLbDesc->AddSubKey( s_pRatingKey->MakeCopy() );
pLbDesc->SetString( "besttime/type", "uint64" );
return pLbDesc;
}
if ( IsPC() || IsPS3() )
{
KeyValues *pSettings = KeyValues::FromString( "SteamLeaderboard",
" :score besttime " // :score is the leaderboard value mapped to game name "besttime"
);
pSettings->SetInt( ":sort", k_ELeaderboardSortMethodDescending ); // Sort order when fetching and displaying leaderboard data
pSettings->SetInt( ":format", k_ELeaderboardDisplayTypeTimeMilliSeconds ); // Note: this is actually 1/100th seconds type, Steam change pending
pSettings->SetInt( ":upload", k_ELeaderboardUploadScoreMethodKeepBest ); // Upload method when writing to leaderboard
return pSettings;
}
}
*/
return NULL;
}

1177
matchmaking/datacenter.cpp Normal file

File diff suppressed because it is too large Load Diff

187
matchmaking/datacenter.h Normal file
View File

@@ -0,0 +1,187 @@
//========= Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef _DATACENTER_H_
#define _DATACENTER_H_
#include "utlvector.h"
#include "utlmap.h"
#include "x360_xlsp_cmd.h"
class CDatacenterCmdBatchImpl;
class CDatacenter :
public IDatacenter,
public IMatchEventsSink
{
public :
CDatacenter();
virtual ~CDatacenter();
//
// IDatacenter implementation
//
public:
//
// EnableUpdate
// controls whether data can be updated in the background
//
virtual void EnableUpdate( bool bEnable );
//
// GetStats
// retrieves the last received datacenter stats
//
virtual KeyValues * GetDataInfo();
virtual KeyValues * GetStats();
//
// CreateCmdBatch
// creates a new instance of cmd batch to communicate
// with datacenter backend
//
virtual IDatacenterCmdBatch * CreateCmdBatch( bool bMustSupportPII );
//
// CanReachDatacenter
// returns true if we were able to establish a connection with the
// datacenter backend regardless if it returned valid data or not.
virtual bool CanReachDatacenter();
// IMatchEventsSink
public:
virtual void OnEvent( KeyValues *pEvent );
protected:
void StorageDeviceWriteInfo( int iCtrlr );
void TrySaveInfoToUserStorage();
void OnDatacenterInfoUpdated();
//
// Interface for match system
//
public:
void Update();
void OnCmdBatchReleased( CDatacenterCmdBatchImpl *pCmdBatch );
protected:
void RequestStart();
void RequestUpdate();
void RequestStop();
void PushAwayNextUpdate();
void OnStorageDeviceAvailable( int iCtrlr );
protected:
KeyValues *m_pDataInfo;
KeyValues *m_pInfoChunks;
#ifdef _X360
CXlspConnection *m_pXlspConnection;
CXlspConnectionCmdBatch *m_pXlspBatch;
bool m_bStorageDeviceAvail[ XUSER_MAX_COUNT ];
int m_nVersionStored;
int m_nVersionApplied;
int m_numDelayedMountAttempts;
float m_flDcRequestDelayUntil;
#elif !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR ) && !defined( SWDS )
friend class CGCClientJobDataRequest;
JobID_t m_JobIDDataRequest;
#endif
float m_flNextSearchTime;
bool m_bCanReachDatacenter;
enum State_t
{
STATE_IDLE,
STATE_REQUESTING_DATA,
STATE_REQUESTING_CHUNKS,
STATE_PAUSED,
};
State_t m_eState;
CUtlVector< CDatacenterCmdBatchImpl * > m_arrCmdBatchObjects;
};
class CDatacenterCmdBatchImpl : public IDatacenterCmdBatch
{
public:
explicit CDatacenterCmdBatchImpl( CDatacenter *pParent, bool bMustSupportPII );
public:
//
// AddCommand
// enqueues a command in command batch queue
//
virtual void AddCommand( KeyValues *pCommand );
//
// IsFinished
// whether command batch queue has finished running / error occurred
//
virtual bool IsFinished();
//
// GetNumResults
// returns number of results retrieved for which data is available
//
virtual int GetNumResults();
//
// GetResult
// returns the result by index
//
virtual KeyValues * GetResult( int idx );
//
// Destroy
// destroys the command batch object and all contained results
//
virtual void Destroy();
//
// SetDestroyWhenFinished
// destroys the command batch object automatically after
// it finishes communication with datacenter
//
virtual void SetDestroyWhenFinished( bool bDestroyWhenFinished );
//
// SetNumRetriesAllowedPerCmd
// configures retry attempts per command
//
virtual void SetNumRetriesAllowedPerCmd( int numRetriesAllowed );
//
// SetRetryCmdTimeout
// configures retry timeout per command
//
virtual void SetRetryCmdTimeout( float flRetryCmdTimeout );
public:
virtual void Update();
protected:
#ifdef _X360
CXlspConnection *m_pXlspConnection;
CXlspConnectionCmdBatch *m_pXlspBatch;
#endif
CDatacenter *m_pParent;
CUtlVector< KeyValues * > m_arrCommands;
int m_numRetriesAllowedPerCmd;
float m_flRetryCmdTimeout;
bool m_bDestroyWhenFinished;
bool m_bMustSupportPII;
};
extern class CDatacenter *g_pDatacenter;
#endif // _DATACENTER_H_

View File

@@ -0,0 +1,378 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "mm_title_richpresence.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CMatchTitle::CMatchTitle()
{
;
}
CMatchTitle::~CMatchTitle()
{
;
}
//
// Init / shutdown
//
InitReturnVal_t CMatchTitle::Init()
{
if ( IGameEventManager2 *mgr = g_pMatchExtensions->GetIGameEventManager2() )
{
mgr->AddListener( this, "server_pre_shutdown", false );
mgr->AddListener( this, "game_newmap", false );
mgr->AddListener( this, "finale_start", false );
mgr->AddListener( this, "round_start", false );
mgr->AddListener( this, "round_end", false );
mgr->AddListener( this, "difficulty_changed", false );
}
return INIT_OK;
}
void CMatchTitle::Shutdown()
{
if ( IGameEventManager2 *mgr = g_pMatchExtensions->GetIGameEventManager2() )
{
mgr->RemoveListener( this );
}
}
//
// Implementation
//
uint64 CMatchTitle::GetTitleID()
{
#ifdef _X360
return 0xFFFFFFFFFFFFFFFF;
#elif !defined( SWDS )
static uint64 uiAppID = 0ull;
if ( !uiAppID && steamapicontext && steamapicontext->SteamUtils() )
{
uiAppID = steamapicontext->SteamUtils()->GetAppID();
}
return uiAppID;
#else
return 0ull;
#endif
}
uint64 CMatchTitle::GetTitleServiceID()
{
#ifdef _X360
return 0x45410880ull; // Left 4 Dead 1 Service ID
#else
return 0ull;
#endif
}
bool CMatchTitle::IsMultiplayer()
{
return true;
}
void CMatchTitle::PrepareNetStartupParams( void *pNetStartupParams )
{
#ifdef _X360
XNetStartupParams &xnsp = *( XNetStartupParams * ) pNetStartupParams;
xnsp.cfgQosDataLimitDiv4 = 64; // 256 bytes
xnsp.cfgSockDefaultRecvBufsizeInK = 64; // Increase receive size for UDP to 64k
xnsp.cfgSockDefaultSendBufsizeInK = 64; // Keep send size at 64k too
int numGamePlayersMax = GetTotalNumPlayersSupported();
int numConnections = 4 * ( numGamePlayersMax - 1 );
// - the max number of connections to members of your game party
// - the max number of connections to members of your social party
// - the max number of connections to a pending game party (if you are joining a new one ).
// - matchmakings client info structure also creates a connection per client for the lobby.
// 1 - the main game session
int numTotalConnections = 1 + numConnections;
// 29 - total Connections (XNADDR/XNKID pairs) ,using 5 sessions (XNKID/XNKEY pairs).
xnsp.cfgKeyRegMax = 16; //adding some extra room because of lazy dealocation of these pairs.
xnsp.cfgSecRegMax = MAX( 64, numTotalConnections ); //adding some extra room because of lazy dealocation of these pairs.
xnsp.cfgSockMaxDgramSockets = xnsp.cfgSecRegMax;
xnsp.cfgSockMaxStreamSockets = xnsp.cfgSecRegMax;
#endif
}
int CMatchTitle::GetTotalNumPlayersSupported()
{
return 1;
}
// Get a guest player name
char const * CMatchTitle::GetGuestPlayerName( int iUserIndex )
{
if ( vgui::ILocalize *pLocalize = g_pMatchExtensions->GetILocalize() )
{
if ( wchar_t* wStringTableEntry = pLocalize->Find( "#L4D360UI_Character_Guest" ) )
{
static char szName[ MAX_PLAYER_NAME_LENGTH ] = {0};
pLocalize->ConvertUnicodeToANSI( wStringTableEntry, szName, ARRAYSIZE( szName ) );
return szName;
}
}
return "";
}
// Sets up all necessary client-side convars and user info before
// connecting to server
void CMatchTitle::PrepareClientForConnect( KeyValues *pSettings )
{
#ifndef SWDS
int numPlayers = 1;
#ifdef _X360
numPlayers = XBX_GetNumGameUsers();
#endif
//
// Now we set the convars
//
for ( int k = 0; k < numPlayers; ++ k )
{
int iController = k;
#ifdef _X360
iController = XBX_GetUserId( k );
#endif
IPlayerLocal *pPlayerLocal = g_pPlayerManager->GetLocalPlayer( iController );
if ( !pPlayerLocal )
continue;
// Set "name"
static SplitScreenConVarRef s_cl_name( "name" );
char const *szName = pPlayerLocal->GetName();
s_cl_name.SetValue( k, szName );
// Set "networkid_force"
if ( IsX360() )
{
static SplitScreenConVarRef s_networkid_force( "networkid_force" );
uint64 xid = pPlayerLocal->GetXUID();
s_networkid_force.SetValue( k, CFmtStr( "%08X:%08X:", uint32( xid >> 32 ), uint32( xid ) ) );
}
}
#endif
}
bool CMatchTitle::StartServerMap( KeyValues *pSettings )
{
int numPlayers = 1;
#ifdef _X360
numPlayers = XBX_GetNumGameUsers();
#endif
char const *szMap = pSettings->GetString( "game/bspname", NULL );
if ( !szMap )
return false;
// Check that we have the server interface and that the map is valid
if ( !g_pMatchExtensions->GetIVEngineServer() )
return false;
if ( !g_pMatchExtensions->GetIVEngineServer()->IsMapValid( szMap ) )
return false;
//
// Prepare game dll reservation package
//
KeyValues *pGameDllReserve = g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForReservation( pSettings );
KeyValues::AutoDelete autodelete( pGameDllReserve );
pGameDllReserve->SetString( "map/mapcommand", ( numPlayers <= 1 ) ? "map" : "ss_map" );
if ( !Q_stricmp( "commentary", pSettings->GetString( "options/play", "" ) ) )
pGameDllReserve->SetString( "map/mapcommand", "map_commentary" );
// Run map based off the faked reservation packet
g_pMatchExtensions->GetIServerGameDLL()->ApplyGameSettings( pGameDllReserve );
return true;
}
static KeyValues * GetCurrentMatchSessionSettings()
{
IMatchSession *pIMatchSession = g_pMatchFramework->GetMatchSession();
return pIMatchSession ? pIMatchSession->GetSessionSettings() : NULL;
}
void CMatchTitle::OnEvent( KeyValues *pEvent )
{
char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "OnPlayerRemoved", szEvent ) ||
!Q_stricmp( "OnPlayerUpdated", szEvent ) )
{
MM_Title_RichPresence_PlayersChanged( GetCurrentMatchSessionSettings() );
}
else if ( !Q_stricmp( "OnMatchSessionUpdate", szEvent ) )
{
if ( !Q_stricmp( pEvent->GetString( "state" ), "updated" ) )
{
if ( KeyValues *kvUpdate = pEvent->FindKey( "update" ) )
{
MM_Title_RichPresence_Update( GetCurrentMatchSessionSettings(), kvUpdate );
}
}
else if ( !Q_stricmp( pEvent->GetString( "state" ), "created" ) ||
!Q_stricmp( pEvent->GetString( "state" ), "ready" ) )
{
MM_Title_RichPresence_Update( GetCurrentMatchSessionSettings(), NULL );
}
else if ( !Q_stricmp( pEvent->GetString( "state" ), "closed" ) )
{
MM_Title_RichPresence_Update( NULL, NULL );
}
}
else if ( !Q_stricmp( "Client::CmdKeyValues", szEvent ) )
{
KeyValues *pCmd = pEvent->GetFirstTrueSubKey();
if ( !pCmd )
return;
char const *szCmd = pCmd->GetName();
if ( !Q_stricmp( "swapteam", szCmd ) )
{
if ( IMatchSession *pIMatchSession = g_pMatchFramework->GetMatchSession() )
{
KeyValues *pRequest = new KeyValues( "Game::Team" );
KeyValues::AutoDelete autodelete( pRequest );
// If a player is on infected team, then the avatar is infected
char const *szTeam = pCmd->GetString( "team" );
// Parse out the team and avatar info
pRequest->SetString( "run", "host" );
pRequest->SetUint64( "xuid", pCmd->GetUint64( "xuid" ) );
pRequest->SetString( "team", szTeam );
pIMatchSession->Command( pRequest );
}
}
}
}
//
//
//
int CMatchTitle::GetEventDebugID( void )
{
return EVENT_DEBUG_ID_INIT;
}
void CMatchTitle::FireGameEvent( IGameEvent *pIGameEvent )
{
// Check if the current match session is on an active game server
IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession();
if ( !pMatchSession )
return;
KeyValues *pSessionSettings = pMatchSession->GetSessionSettings();
char const *szGameServer = pSessionSettings->GetString( "server/server", "" );
char const *szSystemLock = pSessionSettings->GetString( "system/lock", "" );
if ( ( !szGameServer || !*szGameServer ) &&
( !szSystemLock || !*szSystemLock ) )
return;
// Also don't run on the client when there's a host
char const *szSessionType = pMatchSession->GetSessionSystemData()->GetString( "type", NULL );
if ( szSessionType && !Q_stricmp( szSessionType, "client" ) )
return;
// Parse the game event
char const *szGameEvent = pIGameEvent->GetName();
if ( !szGameEvent || !*szGameEvent )
return;
if ( !Q_stricmp( "round_start", szGameEvent ) )
{
pMatchSession->UpdateSessionSettings( KeyValues::AutoDeleteInline( KeyValues::FromString(
"update",
" update { "
" game { "
" state game "
" } "
" } "
) ) );
}
else if ( !Q_stricmp( "finale_start", szGameEvent ) )
{
pMatchSession->UpdateSessionSettings( KeyValues::AutoDeleteInline( KeyValues::FromString(
"update",
" update { "
" game { "
" state finale "
" } "
" } "
) ) );
}
else if ( !Q_stricmp( "game_newmap", szGameEvent ) )
{
KeyValues *kvUpdate = KeyValues::FromString(
"update",
" update { "
" game { "
" state game "
" } "
" } "
);
KeyValues::AutoDelete autodelete( kvUpdate );
pMatchSession->UpdateSessionSettings( kvUpdate );
}
else if ( !Q_stricmp( "difficulty_changed", szGameEvent ) )
{
char const *szDifficulty = pIGameEvent->GetString( "strDifficulty", "normal" );
KeyValues *kvUpdate = KeyValues::FromString(
"update",
" update { "
" game { "
" difficulty = "
" } "
" } "
);
KeyValues::AutoDelete autodelete( kvUpdate );
kvUpdate->SetString( "update/game/difficulty", szDifficulty );
pMatchSession->UpdateSessionSettings( kvUpdate );
}
else if ( !Q_stricmp( "server_pre_shutdown", szGameEvent ) )
{
char const *szReason = pIGameEvent->GetString( "reason", "quit" );
if ( !Q_stricmp( szReason, "quit" ) )
{
DevMsg( "Received server_pre_shutdown notification - server is shutting down...\n" );
// Transform the server shutdown event into game end event
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnEngineDisconnectReason", "reason", "Server shutting down"
) );
}
}
}

View File

@@ -0,0 +1,83 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_TITLE_H
#define MM_TITLE_H
#ifdef _WIN32
#pragma once
#endif
#include "../mm_framework.h"
class CMatchTitle :
public IMatchTitle,
public IMatchEventsSink,
public IGameEventListener2
{
// Methods exposed for the framework
public:
virtual InitReturnVal_t Init();
virtual void Shutdown();
// Methods of IMatchTitle
public:
// Title ID
virtual uint64 GetTitleID();
// Service ID for XLSP
virtual uint64 GetTitleServiceID();
// Whether we are a single-player title or multi-player title
virtual bool IsMultiplayer();
// Prepare network startup params for the title
virtual void PrepareNetStartupParams( void *pNetStartupParams );
// Get total number of players supported by the title
virtual int GetTotalNumPlayersSupported();
// Get a guest player name
virtual char const * GetGuestPlayerName( int iUserIndex );
// Decipher title data fields
virtual TitleDataFieldsDescription_t const * DescribeTitleDataStorage();
// Title achievements
virtual TitleAchievementsDescription_t const * DescribeTitleAchievements();
// Title avatar awards
virtual TitleAvatarAwardsDescription_t const * DescribeTitleAvatarAwards();
// Title leaderboards
virtual KeyValues * DescribeTitleLeaderboard( char const *szLeaderboardView );
// Sets up all necessary client-side convars and user info before
// connecting to server
virtual void PrepareClientForConnect( KeyValues *pSettings );
// Start up a listen server with the given settings
virtual bool StartServerMap( KeyValues *pSettings );
// Methods of IMatchEventsSink
public:
virtual void OnEvent( KeyValues *pEvent );
// Methods of IGameEventListener2
public:
// FireEvent is called by EventManager if event just occured
// KeyValue memory will be freed by manager if not needed anymore
virtual void FireGameEvent( IGameEvent *event );
virtual int GetEventDebugID( void );
public:
CMatchTitle();
~CMatchTitle();
};
// Match title singleton
extern CMatchTitle *g_pMatchTitle;
#endif // MM_TITLE_H

View File

@@ -0,0 +1,372 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "mm_title_richpresence.h"
#include "vstdlib/random.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
class CMatchTitleGameSettingsMgr : public IMatchTitleGameSettingsMgr
{
public:
// 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 );
// 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 );
// Initializes full game settings from potentially abbreviated game settings
virtual void InitializeGameSettings( KeyValues *pSettings );
// Extends game settings update packet before it gets merged with
// session settings and networked to remote clients
virtual void ExtendGameSettingsUpdateKeys( KeyValues *pSettings, KeyValues *pUpdateDeleteKeys );
// 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 );
};
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
//
{
pDetails->SetString( "game/bspname", CFmtStr( "%s", si.m_szMapName ) );
}
}
// 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 { "
" bspname #empty# "
" } "
);
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" );
}
// Rolls up game details for matches grouping
KeyValues * CMatchTitleGameSettingsMgr::RollupGameDetails( KeyValues *pDetails, KeyValues *pRollup, KeyValues *pQuery )
{
return NULL;
}
// Defines dedicated server search key
KeyValues * CMatchTitleGameSettingsMgr::DefineDedicatedSearchKeys( KeyValues *pSettings )
{
if ( IsPC() )
{
static ConVarRef sv_search_key( "sv_search_key" );
KeyValues *pKeys = new KeyValues( "SearchKeys" );
pKeys->SetString( "gametype", CFmtStr( "%s,sv_search_key_%s%d",
"empty",
sv_search_key.GetString(),
g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() ) );
return pKeys;
}
else
{
return NULL;
}
}
// Defines session search keys for matchmaking
KeyValues * CMatchTitleGameSettingsMgr::DefineSessionSearchKeys( KeyValues *pSettings )
{
MEM_ALLOC_CREDIT();
KeyValues *pResult = new KeyValues( "SessionSearch" );
pResult->SetInt( "numPlayers", pSettings->GetInt( "members/numPlayers", XBX_GetNumGameUsers() ) );
/*
char const *szGameMode = pSettings->GetString( "game/mode", "" );
if ( IsX360() )
{
if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
{
static ContextValue_t values[] = {
{ "versus", SESSION_MATCH_QUERY_PUBLIC_STATE_C___SORT___CHAPTER },
{ "teamversus", SESSION_MATCH_QUERY_TEAM_STATE_C_CHAPTER },
{ "scavenge", SESSION_MATCH_QUERY_PUBLIC_STATE_C_CHAPTER___SORT___ROUNDS },
{ "teamscavenge", SESSION_MATCH_QUERY_TEAM_STATE_C_CHAPTER_ROUNDS },
{ "survival", SESSION_MATCH_QUERY_PUBLIC_STATE_C_CHAPTER },
{ "coop", SESSION_MATCH_QUERY_PUBLIC_STATE_C_DIFF___SORT___CHAPTER },
{ "realism", SESSION_MATCH_QUERY_PUBLIC_STATE_C_DIFF___SORT___CHAPTER },
{ NULL, 0xFFFF },
};
pResult->SetInt( "rule", values->ScanValues( szValue ) );
}
// Set the matchmaking version
pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_MMVERSION ), mm_matchmaking_version.GetInt() );
#ifdef _X360
// Set the installed DLCs masks
uint64 uiDlcsMask = MatchSession_GetDlcInstalledMask();
for ( int k = 1; k <= mm_matchmaking_dlcsquery.GetInt(); ++ k )
{
pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_MMVERSION + k ), !!( uiDlcsMask & ( 1ull << k ) ) );
}
pResult->SetInt( "dlc1", PROPERTY_MMVERSION + 1 );
pResult->SetInt( "dlcN", PROPERTY_MMVERSION + mm_matchmaking_dlcsquery.GetInt() );
#endif
// X_CONTEXT_GAME_TYPE
pResult->SetInt( CFmtStr( "Contexts/%d", X_CONTEXT_GAME_TYPE ), X_CONTEXT_GAME_TYPE_STANDARD );
// X_CONTEXT_GAME_MODE
if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
{
pResult->SetInt( CFmtStr( "Contexts/%d", X_CONTEXT_GAME_MODE ), g_pcv_CONTEXT_GAME_MODE->ScanValues( szValue ) );
}
if ( char const *szValue = pSettings->GetString( "game/state", NULL ) )
{
pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_STATE ), g_pcv_CONTEXT_STATE->ScanValues( szValue ) );
}
if ( char const *szValue = pSettings->GetString( "game/difficulty", NULL ) )
{
if ( !Q_stricmp( "coop", szGameMode ) || !Q_stricmp( "realism", szGameMode ) )
{
pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_DIFFICULTY ), g_pcv_CONTEXT_DIFFICULTY->ScanValues( szValue ) );
}
}
if ( int val = pSettings->GetInt( "game/maxrounds" ) )
{
if ( !Q_stricmp( "scavenge", szGameMode ) || !Q_stricmp( "teamscavenge", szGameMode ) )
{
pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_MAXROUNDS ), val );
}
}
char const *szCampaign = pSettings->GetString( "game/campaign" );
if ( *szCampaign )
{
DWORD dwContext = CONTEXT_CAMPAIGN_UNKNOWN;
if ( KeyValues *pAllMissions = g_pMatchExtL4D->GetAllMissions() )
{
if ( KeyValues *pMission = pAllMissions->FindKey( szCampaign ) )
{
dwContext = pMission->GetInt( "x360ctx", dwContext );
}
}
if ( dwContext != CONTEXT_CAMPAIGN_UNKNOWN )
{
pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_CAMPAIGN ), dwContext );
}
}
if ( int val = pSettings->GetInt( "game/chapter" ) )
{
pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_CHAPTER ), val );
}
}
else
*/
{
if ( char const *szValue = pSettings->GetString( "game/bspname", NULL ) )
{
pResult->SetString( "Filter=/game:bspname", szValue );
}
}
return pResult;
}
// Initializes full game settings from potentially abbreviated game settings
void CMatchTitleGameSettingsMgr::InitializeGameSettings( KeyValues *pSettings )
{
char const *szNetwork = pSettings->GetString( "system/network", "LIVE" );
pSettings->SetString( "game/state", "lobby" );
if ( KeyValues *kv = pSettings->FindKey( "Options", true ) )
{
kv->SetString( "server", "listen" );
}
// Offline games don't need slots and player setup
if ( !Q_stricmp( "offline", szNetwork ) )
return;
//
// Set the number of slots
//
int numSlots = 10;
pSettings->SetInt( "members/numSlots", numSlots );
}
// Extends game settings update packet before it gets merged with
// session settings and networked to remote clients
void CMatchTitleGameSettingsMgr::ExtendGameSettingsUpdateKeys( KeyValues *pSettings, KeyValues *pUpdateDeleteKeys )
{
}
// Prepares system for session creation
KeyValues * CMatchTitleGameSettingsMgr::PrepareForSessionCreate( KeyValues *pSettings )
{
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;
}
static void OnRunCommand_TeamChange( KeyValues *pCommand, KeyValues *pSettings, KeyValues **ppPlayersUpdated )
{
MEM_ALLOC_CREDIT();
XUID xuid = pCommand->GetUint64( "xuid" );
char const *szTeam = pCommand->GetString( "team", "" );
KeyValues *pMembers = pSettings->FindKey( "members" );
if ( !pMembers )
return;
// Find the avatar that is going to be updated
KeyValues *pPlayer = SessionMembersFindPlayer( pSettings, xuid );
if ( !pPlayer )
return;
// Check if the team is the same
if ( !Q_stricmp( szTeam, pPlayer->GetString( "game/team", "" ) ) )
return;
KeyValues *kvGame = pPlayer->FindKey( "game", true );
if ( !kvGame )
return;
// If desired avatar is blank, then no validation required
if ( !*szTeam )
{
kvGame->SetString( "team", "random" );
}
else
{
kvGame->SetString( "team", szTeam );
}
// Notify the sessions of a player update
* ( ppPlayersUpdated ++ ) = pPlayer;
}
// 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::Team", 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 )
{
char const *szAvatar = pCommand->GetString( "team" );
if ( !*szAvatar )
{
// Requesting random is only allowed in unlocked lobby
if ( Q_stricmp( "lobby", pSessionSystemData->GetString( "state", "lobby" ) ) )
return;
if ( *pSettings->GetString( "system/lock" ) )
return;
}
OnRunCommand_TeamChange( pCommand, pSettings, ppPlayersUpdated );
return;
}
}
}

View File

@@ -0,0 +1,39 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "../mm_title_main.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
LINK_MATCHMAKING_LIB();
static CMatchTitle g_MatchTitle;
CMatchTitle *g_pMatchTitle = &g_MatchTitle;
IMatchTitle *g_pIMatchTitle = g_pMatchTitle;
IMatchEventsSink *g_pIMatchTitleEventsSink = g_pMatchTitle;
//
// Init / shutdown
//
InitReturnVal_t MM_Title_Init()
{
return g_pMatchTitle->Init();
}
void MM_Title_Shutdown()
{
if ( g_pMatchTitle )
g_pMatchTitle->Shutdown();
}

View File

@@ -0,0 +1,52 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title_richpresence.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static void SetAllUsersContext( DWORD dwContextId, DWORD dwValue, bool bAsync = true )
{
#ifdef _X360
for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k )
{
if ( XBX_GetUserIsGuest( k ) )
continue;
int iCtrlr = XBX_GetUserId( k );
if ( bAsync )
XUserSetContextEx( iCtrlr, dwContextId, dwValue, MMX360_NewOverlappedDormant() );
else
XUserSetContext( iCtrlr, dwContextId, dwValue );
}
#endif
}
static void SetAllUsersProperty( DWORD dwPropertyId, DWORD cbValue, void const *pvValue )
{
#ifdef _X360
for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k )
{
if ( XBX_GetUserIsGuest( k ) )
continue;
int iCtrlr = XBX_GetUserId( k );
XUserSetPropertyEx( iCtrlr, dwPropertyId, cbValue, pvValue, MMX360_NewOverlappedDormant() );
}
#endif
}
KeyValues * MM_Title_RichPresence_PrepareForSessionCreate( KeyValues *pSettings )
{
return NULL;
}
void MM_Title_RichPresence_Update( KeyValues *pFullSettings, KeyValues *pUpdatedSettings )
{
}
void MM_Title_RichPresence_PlayersChanged( KeyValues *pFullSettings )
{
}

View File

@@ -0,0 +1,20 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_TITLE_RICHPRESENCE_H
#define MM_TITLE_RICHPRESENCE_H
#ifdef _WIN32
#pragma once
#endif
#include "mm_title.h"
void MM_Title_RichPresence_Update( KeyValues *pFullSettings, KeyValues *pUpdatedSettings );
void MM_Title_RichPresence_PlayersChanged( KeyValues *pFullSettings );
KeyValues * MM_Title_RichPresence_PrepareForSessionCreate( KeyValues *pSettings );
#endif // MM_TITLE_H

View File

@@ -0,0 +1,113 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
TitleDataFieldsDescription_t const * CMatchTitle::DescribeTitleDataStorage()
{
#define TD_ENTRY( szName, nTD, eDataType, numBytesOffset ) \
{ szName, TitleDataFieldsDescription_t::nTD, TitleDataFieldsDescription_t::eDataType, numBytesOffset }
static TitleDataFieldsDescription_t tdfd[] =
{
#if 0
TD_ENTRY( "TD1.Easy.Games.Total", DB_TD1, DT_U64, offsetof( TitleData1, mGames[0] ) ),
TD_ENTRY( "TD1.Normal.Games.Total", DB_TD1, DT_U64, offsetof( TitleData1, mGames[1] ) ),
TD_ENTRY( "TD1.Advanced.Games.Total", DB_TD1, DT_U64, offsetof( TitleData1, mGames[2] ) ),
TD_ENTRY( "TD1.Expert.Games.Total", DB_TD1, DT_U64, offsetof( TitleData1, mGames[3] ) ),
#endif
TD_ENTRY( NULL, DB_TD1, DT_U8, 0 )
};
#undef TD_ENTRY
return tdfd;
}
TitleAchievementsDescription_t const * CMatchTitle::DescribeTitleAchievements()
{
static TitleAchievementsDescription_t tad[] =
{
//#include "left4dead2.xhelp.achtitledesc.txt"
// END MARKER
{ NULL, 0 }
};
return tad;
}
TitleAvatarAwardsDescription_t const * CMatchTitle::DescribeTitleAvatarAwards()
{
static TitleAvatarAwardsDescription_t taad[] =
{
//#include "left4dead2.xhelp.avawtitledesc.txt"
// END MARKER
{ NULL, 0 }
};
return taad;
}
// Title leaderboards
KeyValues * CMatchTitle::DescribeTitleLeaderboard( char const *szLeaderboardView )
{
/*
// Check if this is a survival leaderboard
if ( char const *szSurvivalMap = StringAfterPrefix( szLeaderboardView, "survival_" ) )
{
if ( IsX360() )
{
// Find the corresponding record in the mission script
KeyValues *pSettings = new KeyValues( "settings" );
KeyValues::AutoDelete autodelete_pSettings( pSettings );
pSettings->SetString( "game/mode", "survival" );
KeyValues *pMissionInfo = NULL;
KeyValues *pMapInfo = g_pMatchExtL4D->GetMapInfoByBspName( pSettings, szSurvivalMap, &pMissionInfo );
if ( !pMapInfo || !pMissionInfo )
return NULL;
// Find the leaderboard description in the map info
KeyValues *pLbDesc = pMapInfo->FindKey( "x360leaderboard" );
if ( !pLbDesc )
return NULL;
// Insert the required keys
pLbDesc = pLbDesc->MakeCopy();
static KeyValues *s_pRatingKey = KeyValues::FromString( ":rating", // X360 leaderboards are rated
" name besttime " // game name of the rating field is "besttime"
" type uint64 " // type is uint64
" rule max" // rated field must be greater than cached value so that it can be written
);
pLbDesc->AddSubKey( s_pRatingKey->MakeCopy() );
pLbDesc->SetString( "besttime/type", "uint64" );
return pLbDesc;
}
if ( IsPC() )
{
KeyValues *pSettings = KeyValues::FromString( "SteamLeaderboard",
" :score besttime " // :score is the leaderboard value mapped to game name "besttime"
);
pSettings->SetInt( ":sort", k_ELeaderboardSortMethodDescending ); // Sort order when fetching and displaying leaderboard data
pSettings->SetInt( ":format", k_ELeaderboardDisplayTypeTimeMilliSeconds ); // Note: this is actually 1/100th seconds type, Steam change pending
pSettings->SetInt( ":upload", k_ELeaderboardUploadScoreMethodKeepBest ); // Upload method when writing to leaderboard
return pSettings;
}
}
*/
return NULL;
}

868
matchmaking/ds_searcher.cpp Normal file
View File

@@ -0,0 +1,868 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_framework.h"
#include "fmtstr.h"
#include "vstdlib/random.h"
#include "protocol.h"
#include "proto_oob.h"
#include "bitbuf.h"
#include "checksum_crc.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar mm_dedicated_allow( "mm_dedicated_allow", "1", FCVAR_DEVELOPMENTONLY, "1 = allow searches for dedicated servers" );
ConVar mm_dedicated_fake( "mm_dedicated_fake", "0", FCVAR_DEVELOPMENTONLY, "1 = pretend like search is going, but abort after some time" );
ConVar mm_dedicated_force_servers( "mm_dedicated_force_servers", "", FCVAR_RELEASE,
"Comma delimited list of ip:port of servers used to search for dedicated servers instead of searching for public servers.\n"
"Use syntax `publicip1:port|privateip1:port,publicip2:port|privateip2:port` if your server is behind NAT.\n"
"If the server is behind NAT, you can specify `0.0.0.0|privateip:port` and if server port is in the list of `mm_server_search_lan_ports` its public address should be automatically detected." );
ConVar mm_dedicated_ip( "mm_dedicated_ip", "", FCVAR_DEVELOPMENTONLY, "IP address of dedicated servers to consider available" );
ConVar mm_dedicated_timeout_request( "mm_dedicated_timeout_request", "20", FCVAR_DEVELOPMENTONLY );
ConVar mm_dedicated_search_maxping( "mm_dedicated_search_maxping", IsX360() ? "200" : "150", FCVAR_RELEASE | FCVAR_ARCHIVE, "Longest preferred ping to dedicated servers for games", true, 25.f, true, 350.f );
ConVar mm_dedicated_search_maxresults( "mm_dedicated_search_maxresults", "75", FCVAR_DEVELOPMENTONLY );
extern ConVar mm_dedicated_xlsp_timeout;
CDsSearcher::CDsSearcher( KeyValues *pSettings, uint64 uiReserveCookie, IMatchSession *pMatchSession, uint64 ullCrypt ) :
m_pSettings( pSettings ),
m_uiReserveCookie( uiReserveCookie ),
m_pReserveSettings( g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForReservation( m_pSettings ) ),
m_autodelete_pReserveSettings( m_pReserveSettings ),
#ifdef _X360
m_pTitleServers( NULL ),
#elif !defined( NO_STEAM )
m_pServerListListener( NULL ),
m_nSearchPass( 0 ),
#endif
m_eState( STATE_INIT ),
m_flTimeout( 0.0f ),
m_pAsyncOperation( NULL ),
m_pMatchSession( pMatchSession ),
m_ullCrypt( ullCrypt )
{
#ifdef _X360
ZeroMemory( m_chDatacenterQuery, sizeof( m_chDatacenterQuery ) );
ZeroMemory( &m_dc, sizeof( m_dc ) );
#endif
DevMsg( "Created DS searcher\n" );
KeyValuesDumpAsDevMsg( m_pSettings );
memset( &m_Result, 0, sizeof( m_Result ) );
// Build the reservation settings
// Load test
m_bLoadTest = (m_pSettings->GetInt( "options/sv_load_test", 0) == 1);
}
CDsSearcher::~CDsSearcher()
{
;
}
void CDsSearcher::Update()
{
switch ( m_eState )
{
case STATE_INIT:
{
char const *szNetwork = m_pSettings->GetString( "system/network", "" );
char const *szServer = m_pSettings->GetString( "options/server", "listen" );
if ( m_pSettings->GetString( "server/server", NULL ) )
{
InitWithKnownServer();
}
else if ( mm_dedicated_allow.GetBool() &&
!Q_stricmp( "LIVE", szNetwork ) &&
Q_stricmp( "listen", szServer ) &&
!( g_pMatchFramework->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_SETTING_NODEDICATED ) )
{
InitDedicatedSearch();
}
else
{
m_eState = STATE_FINISHED;
}
}
break;
case STATE_WAITING:
{
if ( Plat_FloatTime() > m_flTimeout )
m_eState = STATE_FINISHED;
}
break;
#ifdef _X360
case STATE_XLSP_ENUMERATE_DCS:
m_pTitleServers->Update();
if ( m_pTitleServers->IsSearchCompleted() )
Xlsp_OnEnumerateDcsCompleted();
break;
case STATE_XLSP_NEXT_DC:
Xlsp_StartNextDc();
break;
case STATE_XLSP_REQUESTING_SERVERS:
if ( Plat_FloatTime() > m_flTimeout )
{
DevWarning( "XLSP datacenter `%s` timed out.\n", m_dc.m_szGatewayName );
m_dc.Destroy();
m_eState = STATE_XLSP_NEXT_DC;
}
break;
#elif !defined( NO_STEAM )
case STATE_STEAM_REQUESTING_SERVERS:
if ( Plat_FloatTime() > m_flTimeout )
{
DevWarning( "Steam search for dedicated servers timed out.\n" );
Steam_OnDedicatedServerListFetched();
}
break;
case STATE_STEAM_NEXT_SEARCH_PASS:
Steam_SearchPass();
break;
#endif
}
}
void CDsSearcher::OnEvent( KeyValues *pEvent )
{
char const *szEvent = pEvent->GetName();
#ifdef _X360
if ( m_eState == STATE_XLSP_REQUESTING_SERVERS &&
!Q_stricmp( "M2A_SERVER_BATCH", szEvent ) )
{
void const *pData = pEvent->GetPtr( "ptr" );
int numBytes = pEvent->GetInt( "size" );
Xlsp_OnDcServerBatch( pData, numBytes );
}
#elif !defined( NO_STEAM )
szEvent;
#endif
}
void CDsSearcher::Destroy()
{
if ( m_pAsyncOperation )
{
m_pAsyncOperation->Release();
m_pAsyncOperation = NULL;
}
#ifdef _X360
switch ( m_eState )
{
case STATE_XLSP_ENUMERATE_DCS:
if ( m_pTitleServers )
m_pTitleServers->Destroy();
m_pTitleServers = NULL;
break;
case STATE_XLSP_REQUESTING_SERVERS:
case STATE_RESERVING:
m_dc.Destroy();
break;
}
#elif !defined( NO_STEAM )
if ( m_pServerListListener )
{
m_pServerListListener->Destroy();
m_pServerListListener = NULL;
}
#endif
delete this;
}
bool CDsSearcher::IsFinished()
{
return m_eState == STATE_FINISHED;
}
CDsSearcher::DsResult_t const & CDsSearcher::GetResult()
{
return m_Result;
}
void CDsSearcher::DsResult_t::CopyToServerKey( KeyValues *pKvServer, uint64 ullCrypt ) const
{
Assert( m_bDedicated );
pKvServer->SetString( "server", "dedicated" );
#ifdef _X360
pKvServer->SetString( "adrInsecure", m_szInsecureSendableServerAddress );
#elif !defined( NO_STEAM )
if ( char const *szEncrypted = MatchSession_EncryptAddressString( m_szPublicConnectionString, ullCrypt ) )
pKvServer->SetString( "adronline", szEncrypted );
else
pKvServer->SetString( "adronline", m_szPublicConnectionString );
if ( m_szPrivateConnectionString[0] )
{
if ( char const *szEncrypted = MatchSession_EncryptAddressString( m_szPrivateConnectionString, ullCrypt ) )
pKvServer->SetString( "adrlocal", szEncrypted );
else
pKvServer->SetString( "adrlocal", m_szPrivateConnectionString );
}
#endif
}
//
// Implementation
//
void CDsSearcher::InitDedicatedSearch()
{
if ( mm_dedicated_fake.GetBool() )
{
// Special fake of the search - it just spins for some time and
// pretends like it was aborted
m_flTimeout = Plat_FloatTime() + mm_dedicated_timeout_request.GetFloat();
m_eState = STATE_WAITING;
m_Result.m_bAborted = true;
return;
}
#ifdef _X360
Xlsp_EnumerateDcs();
#elif !defined( NO_STEAM )
m_flTimeout = Plat_FloatTime() + mm_dedicated_timeout_request.GetFloat();
Steam_SearchPass();
#endif
}
void CDsSearcher::InitWithKnownServer()
{
#ifdef _X360
Assert( 0 );
m_eState = STATE_FINISHED;
return;
#elif !defined( NO_STEAM )
if ( m_pSettings->GetInt( "server/reserved" ) )
{
m_Result.m_bDedicated = true;
char const *szAdrOnline = m_pSettings->GetString( "server/adronline", "" );
if ( char const *szDecrypted = MatchSession_DecryptAddressString( szAdrOnline, m_ullCrypt ) )
szAdrOnline = szDecrypted;
Q_strncpy( m_Result.m_szConnectionString, szAdrOnline, ARRAYSIZE( m_Result.m_szConnectionString ) );
Q_strncpy( m_Result.m_szPublicConnectionString, szAdrOnline, ARRAYSIZE( m_Result.m_szPublicConnectionString ) );
char const *szAdrLocal = m_pSettings->GetString( "server/adrlocal", "" );
if ( char const *szDecrypted = MatchSession_DecryptAddressString( szAdrLocal, m_ullCrypt ) )
szAdrLocal = szDecrypted;
Q_strncpy( m_Result.m_szPrivateConnectionString, szAdrLocal, ARRAYSIZE( m_Result.m_szPrivateConnectionString ) );
m_eState = STATE_FINISHED;
}
else
{
DsServer_t dsResult(
m_pSettings->GetString( "server/adronline", "" ),
m_pSettings->GetString( "server/adrlocal", "" ),
0
);
if ( char const *szDecrypted = MatchSession_DecryptAddressString( dsResult.m_szConnectionString, m_ullCrypt ) )
Q_strncpy( dsResult.m_szConnectionString, szDecrypted, ARRAYSIZE( dsResult.m_szConnectionString ) );
if ( char const *szDecrypted = MatchSession_DecryptAddressString( dsResult.m_szPrivateConnectionString, m_ullCrypt ) )
Q_strncpy( dsResult.m_szPrivateConnectionString, szDecrypted, ARRAYSIZE( dsResult.m_szPrivateConnectionString ) );
m_arrServerList.AddToTail( dsResult );
ReserveNextServer();
}
#endif
}
#ifdef _X360
void CDsSearcher::Xlsp_EnumerateDcs()
{
m_eState = STATE_XLSP_ENUMERATE_DCS;
m_pTitleServers = new CXlspTitleServers( mm_dedicated_search_maxping.GetInt(), false );
}
void CDsSearcher::Xlsp_OnEnumerateDcsCompleted()
{
DevMsg( "Xlsp_OnEnumerateDcsCompleted - analyzing QOS results...\n" );
CUtlVector< CXlspDatacenter > &arrDcs = m_pTitleServers->GetDatacenters();
m_arrDatacenters.AddMultipleToTail( arrDcs.Count(), arrDcs.Base() );
m_pTitleServers->Destroy();
m_pTitleServers = NULL;
//
// Sort and randomize the accepted results
//
m_arrDatacenters.Sort( CXlspDatacenter::Compare );
for ( int k = 0; k < m_arrDatacenters.Count() - 1; ++ k )
{
CXlspDatacenter &dc1 = m_arrDatacenters[ k ];
CXlspDatacenter &dc2 = m_arrDatacenters[ k + 1 ];
if ( dc1.m_nPingBucket == dc2.m_nPingBucket && RandomInt( 0, 1 ) )
{
CXlspDatacenter dcSwap = dc1;
dc1 = dc2;
dc2 = dcSwap;
}
}
DevMsg( "Xlsp_OnEnumerateDcsCompleted - accepted %d datacenters.\n", m_arrDatacenters.Count() );
for ( int k = 0; k < m_arrDatacenters.Count(); ++ k )
{
DevMsg( " %d. `%s`\n", k, m_arrDatacenters[k].m_szGatewayName );
}
// Prepare the datacenter query
Xlsp_PrepareDatacenterQuery();
// Go to the next datacenter
m_eState = STATE_XLSP_NEXT_DC;
}
void CDsSearcher::Xlsp_PrepareDatacenterQuery()
{
// Compute CRC of primary user's gamertag
byte bSult = RandomInt( 5, 100 );
CRC32_t crc32 = 0;
if ( IPlayerLocal * player = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() ) )
{
char const *szPlayerName = player->GetName();
crc32 = CRC32_ProcessSingleBuffer( szPlayerName, strlen( szPlayerName ) );
uint32 sult32 = bSult | ( bSult << 8 ) | ( bSult << 16 ) | ( bSult << 24 );
crc32 ^= sult32;
}
if ( !crc32 )
bSult = 0;
// Search key
static ConVarRef sv_search_key( "sv_search_key" );
char const *szPrivateKey = sv_search_key.IsValid() ? sv_search_key.GetString() : "";
if ( !*szPrivateKey )
szPrivateKey = "default";
//
// Build query
//
Q_snprintf( m_chDatacenterQuery, ARRAYSIZE( m_chDatacenterQuery ),
"\\empty\\1"
"\\private\\%s"
"\\players\\%d"
"\\slots\\%d"
"\\perm\\%s"
"\\acct\\%02x%08x",
szPrivateKey,
m_pSettings->GetInt( "members/numPlayers", 0 ),
m_pSettings->GetInt( "members/numSlots", 0 ),
m_pSettings->GetString( "system/access", "public" ),
bSult, crc32
);
DevMsg( "Datacenters query: %s\n", m_chDatacenterQuery );
}
void CDsSearcher::Xlsp_StartNextDc()
{
if ( !m_arrDatacenters.Count() )
{
m_eState = STATE_FINISHED;
return;
}
//
// Get the next datacenter off the list
//
m_dc = m_arrDatacenters.Head();
m_arrDatacenters.RemoveMultipleFromHead( 1 );
m_flTimeout = Plat_FloatTime() + mm_dedicated_xlsp_timeout.GetFloat();
DevMsg( "[XLSP] Requesting server batch from %s:%d (%d masters) - ping %d [<= %d]\n"
" ProbesXmit=%3d ProbesRecv=%3d\n"
" RttMinInMsecs=%3d RttMedInMsecs=%3d\n"
" UpBitsPerSec=%6d DnBitsPerSec=%6d\n",
m_dc.m_szGatewayName, m_dc.m_nMasterServerPortStart, m_dc.m_numMasterServers, m_dc.m_qos.wRttMedInMsecs, m_dc.m_nPingBucket,
m_dc.m_qos.cProbesXmit, m_dc.m_qos.cProbesRecv,
m_dc.m_qos.wRttMinInMsecs, m_dc.m_qos.wRttMedInMsecs,
m_dc.m_qos.dwUpBitsPerSec, m_dc.m_qos.dwDnBitsPerSec );
if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
{
m_dc.m_adrSecure = m_dc.m_xsi.inaServer;
}
else
{
//
// Resolve the secure address
//
DWORD ret = g_pMatchExtensions->GetIXOnline()->XNetServerToInAddr( m_dc.m_xsi.inaServer, g_pMatchFramework->GetMatchTitle()->GetTitleServiceID(), &m_dc.m_adrSecure );
if ( ret != ERROR_SUCCESS )
{
DevWarning( "Failed to resolve XLSP secure address (code = 0x%08X)!\n", ret );
return;
}
}
// Convert to netadr_t on a random master port
netadr_t inetAddr;
inetAddr.SetType( NA_IP );
inetAddr.SetIPAndPort( m_dc.m_adrSecure.s_addr,
m_dc.m_nMasterServerPortStart + RandomInt( 0, m_dc.m_numMasterServers - 1 ) );
//
// Prepare the request payload
//
char msg_buffer[ INetSupport::NC_MAX_ROUTABLE_PAYLOAD ];
bf_write msg( msg_buffer, sizeof( msg_buffer ) );
msg.WriteByte( A2M_GET_SERVERS_BATCH2 );
msg.WriteByte( '\n' );
msg.WriteLong( 0 ); // batch starts at 0
msg.WriteLong( m_dc.m_adrSecure.s_addr ); // datacenter's challenge
msg.WriteString( m_chDatacenterQuery ); // datacenter query
msg.WriteByte( '\n' );
g_pMatchExtensions->GetINetSupport()->SendPacket( NULL, INetSupport::NS_SOCK_CLIENT,
inetAddr, msg.GetData(), msg.GetNumBytesWritten() );
m_eState = STATE_XLSP_REQUESTING_SERVERS;
}
void CDsSearcher::Xlsp_OnDcServerBatch( void const *pData, int numBytes )
{
if ( numBytes < 8 )
return;
bf_read msg( pData, numBytes );
int nNextId = msg.ReadLong();
nNextId;
uint nChallenge = msg.ReadLong();
if ( nChallenge != m_dc.m_adrSecure.s_addr )
return;
//
// Get master server reply message or Secure Gateway name (must match request)
//
char szReply[ MAX_PATH ] = {0};
msg.ReadString( szReply, ARRAYSIZE( szReply ), true );
if ( !szReply[0] )
{
DevWarning( "XLSP master server: empty response.\n" );
m_dc.Destroy();
m_eState = STATE_XLSP_NEXT_DC;
return;
}
if ( !Q_stricmp( "##full", szReply ) )
{
DevWarning( "XLSP master server: full.\n" );
m_dc.Destroy();
m_eState = STATE_XLSP_NEXT_DC;
return;
}
if ( !Q_stricmp( "##local", szReply ) )
{
DevWarning( "XLSP master server: game is not eligible for dedicated server.\n" );
m_dc.Destroy();
m_eState = STATE_FINISHED;
return;
}
// Bypass the gateway name check if we're faking it.
if ( !CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
{
if ( Q_stricmp( m_dc.m_szGatewayName, szReply ) )
{
DevWarning( "XLSP master server: wrong reply `%s`, expected gateway `%s`.\n", szReply, m_dc.m_szGatewayName );
m_dc.Destroy();
m_eState = STATE_XLSP_NEXT_DC;
return;
}
}
//
// Process all the servers in the batch
//
m_arrServerPorts.RemoveAll();
for ( ; ; )
{
uint16 nPort = msg.ReadWord();
if ( !nPort || nPort == 0xFFFF )
{
// end of list
break;
}
m_arrServerPorts.AddToTail( nPort );
}
DevWarning( "XLSP master server: returned %d servers in batch.\n", m_arrServerPorts.Count() );
// Go ahead and start reserving
ReserveNextServer();
}
#elif !defined( NO_STEAM )
void CDsSearcher::Steam_SearchPass()
{
if ( mm_dedicated_force_servers.GetString()[0] )
{
// if convar is on to force dedicated server choices, pretend we got search results of just those servers
CSplitString serverList( mm_dedicated_force_servers.GetString(), "," );
for ( int i = 0; i < serverList.Count(); i++ )
{
// Check if the specification has a private IP address
char const * adrsStrings[2] = { serverList[i], "" };
if ( char *pchDelim = strchr( serverList[i], '|' ) )
{
*( pchDelim ++ ) = 0;
adrsStrings[1] = pchDelim;
}
netadr_t adrsForced[2];
adrsForced[0].SetFromString( adrsStrings[0] );
adrsForced[1].SetFromString( adrsStrings[1] );
// Check if a locally discovered server is known with
// either public or private address that is being forced
int numServers = g_pServerManager->GetNumServers();
for ( int iServer = 0; iServer < numServers; ++ iServer )
{
IMatchServer *pMatchServer = g_pServerManager->GetServerByIndex( iServer );
if ( !pMatchServer )
continue;
KeyValues *pServerDetails = pMatchServer->GetGameDetails();
netadr_t adrsKnown[2];
char const * adrsStringsKnown[2] = { pServerDetails->GetString( "server/adronline" ), pServerDetails->GetString( "server/adrlocal" ) };
adrsKnown[0].SetFromString( adrsStringsKnown[0] );
adrsKnown[1].SetFromString( adrsStringsKnown[1] );
for ( int iAdrForced = 0; iAdrForced < ARRAYSIZE( adrsForced ); ++ iAdrForced )
{
for ( int iAdrKnown = 0; iAdrKnown < ARRAYSIZE( adrsKnown ); ++ iAdrKnown )
{
if ( adrsForced[iAdrForced].GetIPHostByteOrder() && adrsKnown[iAdrKnown].GetIPHostByteOrder() &&
adrsForced[iAdrForced].CompareAdr( adrsKnown[iAdrKnown] ) )
{
if ( !adrsForced[!iAdrForced].GetIPHostByteOrder() ) // user not forcing other address, but we know it
adrsStrings[!iAdrForced] = adrsStringsKnown[!iAdrKnown];
goto finished_server_lookup;
}
}
}
}
finished_server_lookup:
m_arrServerList.AddToTail( CDsSearcher::DsServer_t(
adrsStrings[0], adrsStrings[1],
0 ) ); // you get no accurate ping to forced servers
}
Steam_OnDedicatedServerListFetched();
return;
}
CUtlVector< MatchMakingKeyValuePair_t > filters;
filters.EnsureCapacity( 10 );
// filter by game and require empty server
filters.AddToTail( MatchMakingKeyValuePair_t( "gamedir", COM_GetModDirectory() ) );
filters.AddToTail( MatchMakingKeyValuePair_t( "noplayers", "1" ) );
// Official servers
bool bNeedOfficialServer = false;
char const *szServerType = m_pSettings->GetString( "options/server", NULL );
if ( szServerType && !Q_stricmp( szServerType, "official" ) )
{
bNeedOfficialServer = true;
filters.AddToTail( MatchMakingKeyValuePair_t( "white", "1" ) );
}
// Allow the game to extend the filters
if ( KeyValues *pExtra = g_pMMF->GetMatchTitleGameSettingsMgr()->DefineDedicatedSearchKeys( m_pSettings, bNeedOfficialServer, m_nSearchPass ) )
{
if ( !mm_dedicated_ip.GetString()[0] )
{
for ( KeyValues *val = pExtra->GetFirstValue(); val; val = val->GetNextValue() )
{
char const *szValue = val->GetString();
if ( !szValue || !*szValue )
continue;
filters.AddToTail( MatchMakingKeyValuePair_t( val->GetName(), szValue ) );
}
}
pExtra->deleteThis();
}
// Load test
if (m_bLoadTest && m_nSearchPass == 0)
{
// Add to the "gametype" filter
for ( int i=0; i < filters.Count(); i++ )
{
MatchMakingKeyValuePair_t *pKV = &(filters[i]);
if ( !Q_stricmp( pKV->m_szKey, "gametype" ) )
{
Q_strncat( pKV->m_szValue, ",sv_load_test", sizeof(pKV->m_szValue) );
break;
}
}
}
// request the server list. We will get called back at ServerResponded, ServerFailedToRespond, and RefreshComplete
m_eState = STATE_STEAM_REQUESTING_SERVERS;
m_pServerListListener = new CServerListListener( this, filters );
++m_nSearchPass;
}
CDsSearcher::CServerListListener::CServerListListener( CDsSearcher *pDsSearcher, CUtlVector< MatchMakingKeyValuePair_t > &filters ) :
m_pOuter( pDsSearcher ),
m_hRequest( NULL )
{
MatchMakingKeyValuePair_t *pFilter = filters.Base();
DevMsg( 1, "Requesting dedicated server list...\n" );
for (int i = 0; i < filters.Count(); i++)
{
DevMsg("Filter %d: %s=%s\n", i, filters.Element(i).m_szKey, filters.Element(i).m_szValue);
}
m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestInternetServerList(
( AppId_t ) g_pMatchFramework->GetMatchTitle()->GetTitleID(), &pFilter,
filters.Count(), this );
}
void CDsSearcher::CServerListListener::Destroy()
{
m_pOuter = NULL;
if ( m_hRequest )
steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest );
m_hRequest = NULL;
delete this;
}
void CDsSearcher::CServerListListener::ServerResponded( HServerListRequest hReq, int iServer )
{
gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers()
->GetServerDetails( hReq, iServer );
Msg( "[MM] Server responded '%s', dist %d\n",
pServer->m_NetAdr.GetConnectionAddressString(), pServer->m_nPing );
// Check if the server IP address matches
char const *szDedicatedIp = mm_dedicated_ip.GetString();
if ( szDedicatedIp && szDedicatedIp[0] )
{
char const *szServerConnString = pServer->m_NetAdr.GetConnectionAddressString();
szServerConnString = StringAfterPrefix( szServerConnString, szDedicatedIp );
if ( !szServerConnString ||
( szServerConnString[0] &&
szServerConnString[0] != ':' ) )
{
DevMsg( " rejected dedicated server '%s' due to ip filter '%s'\n",
pServer->m_NetAdr.GetConnectionAddressString(), szDedicatedIp );
return;
}
}
// Register the dedicated server as acceptable
// netadr_t adrPublic, adrPrivate;
// adrPublic.SetFromString( pServer->m_NetAdr.GetConnectionAddressString() );
// TODO: bool bHasPrivate = FindLANServerPrivateIPByPublicIP( adrPublic, adrPrivate );
// Don't reserve servers with human players playing
int nHumanPlayers = pServer->m_nPlayers - pServer->m_nBotPlayers;
if ( nHumanPlayers > 0 )
return;
// Don't reserve servers with too high ping
if ( ( mm_dedicated_search_maxping.GetInt() > 0 ) && ( pServer->m_nPing > mm_dedicated_search_maxping.GetInt() ) )
return;
// Register the result
if ( m_pOuter )
{
// See if maybe we know about a private address for this server
char const *szPrivateAddress = "";
netadr_t adrPublic;
adrPublic.SetFromString( pServer->m_NetAdr.GetConnectionAddressString() );
XUID xuidOnline = uint64( adrPublic.GetIPNetworkByteOrder() ) | ( uint64( adrPublic.GetPort() ) << 32ull );
if ( IMatchServer *pMatchServer = g_pServerManager->GetServerByOnlineId( xuidOnline ) )
{
szPrivateAddress = pMatchServer->GetGameDetails()->GetString( "server/adrlocal" );
}
m_pOuter->m_arrServerList.AddToTail( CDsSearcher::DsServer_t(
pServer->m_NetAdr.GetConnectionAddressString(),
szPrivateAddress,
pServer->m_nPing ) );
if ( m_pOuter->m_arrServerList.Count() > mm_dedicated_search_maxresults.GetInt() ||
Plat_FloatTime() > m_pOuter->m_flTimeout )
{
steamapicontext->SteamMatchmakingServers()->CancelQuery( hReq );
}
}
}
void CDsSearcher::CServerListListener::RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response )
{
if ( m_pOuter )
{
m_pOuter->Steam_OnDedicatedServerListFetched();
}
}
void CDsSearcher::Steam_OnDedicatedServerListFetched()
{
if ( m_bLoadTest && m_nSearchPass < 2 )
{
m_eState = STATE_INIT;
}
else
{
if ( m_pServerListListener )
{
m_pServerListListener->Destroy();
m_pServerListListener = NULL;
}
DevMsg( 1, "Dedicated server list fetched %d servers.\n", m_arrServerList.Count() );
ReserveNextServer();
}
}
#endif
void CDsSearcher::ReserveNextServer()
{
m_eState = STATE_RESERVING;
#ifdef _X360
if ( !m_arrServerPorts.Count() )
{
m_dc.Destroy();
m_eState = STATE_XLSP_NEXT_DC;
return;
}
uint16 nPort = m_arrServerPorts.Head();
m_arrServerPorts.RemoveMultipleFromHead( 1 );
netadr_t inetAddrSecure;
inetAddrSecure.SetType( NA_IP );
inetAddrSecure.SetIPAndPort( m_dc.m_adrSecure.s_addr, nPort );
netadr_t inetAddrInsecureSendable;
inetAddrInsecureSendable.SetType( NA_IP );
inetAddrInsecureSendable.SetIPAndPort( m_dc.m_xsi.inaServer.s_addr, nPort );
Q_strncpy( m_Result.m_szConnectionString, inetAddrSecure.ToString(), ARRAYSIZE( m_Result.m_szConnectionString ) );
Q_strncpy( m_Result.m_szInsecureSendableServerAddress, inetAddrInsecureSendable.ToString(), ARRAYSIZE( m_Result.m_szInsecureSendableServerAddress ) );
netadr_t addrPublic, addrPrivate;
addrPrivate.SetType( NA_NULL );
addrPublic = inetAddrSecure;
#elif !defined( NO_STEAM )
if ( !m_arrServerList.Count() )
{
if ( Plat_FloatTime() < m_flTimeout )
{
m_eState = STATE_STEAM_NEXT_SEARCH_PASS;
return;
}
else
{
m_eState = STATE_FINISHED;
return;
}
}
DsServer_t dss = m_arrServerList.Head();
m_arrServerList.RemoveMultipleFromHead( 1 );
Q_strncpy( m_Result.m_szPublicConnectionString, dss.m_szConnectionString, ARRAYSIZE( m_Result.m_szPublicConnectionString ) );
Q_strncpy( m_Result.m_szPrivateConnectionString, dss.m_szPrivateConnectionString, ARRAYSIZE( m_Result.m_szPrivateConnectionString ) );
netadr_t addrPublic, addrPrivate;
addrPublic.SetFromString( dss.m_szConnectionString );
if ( dss.m_szPrivateConnectionString[0] )
addrPrivate.SetFromString( dss.m_szPrivateConnectionString );
else
addrPrivate.SetType( NA_NULL );
#else
netadr_t addrPublic, addrPrivate;
#endif
g_pMatchExtensions->GetINetSupport()->ReserveServer( addrPublic, addrPrivate,
m_uiReserveCookie, m_pReserveSettings,
this, &m_pAsyncOperation );
}
void CDsSearcher::OnOperationFinished( IMatchAsyncOperation *pOperation )
{
Assert( pOperation == m_pAsyncOperation );
uint64 uiResult = m_pAsyncOperation->GetResult();
if ( m_pAsyncOperation->GetState() == AOS_FAILED || !uiResult )
{
ReserveNextServer();
}
else
{
m_Result.m_bDedicated = true;
char const *szReservedAddr = ( char const * )( int )uiResult;
Q_strncpy( m_Result.m_szConnectionString, szReservedAddr, ARRAYSIZE( m_Result.m_szConnectionString ) );
// If this server reservation reported number of game slots then
// force that setting into session data
uint32 numGameSlots = uint32( pOperation->GetResultExtraInfo() & 0xFF );
if ( m_pMatchSession && ( numGameSlots > 0 ) )
{
KeyValues *kvUpdate = new KeyValues( "update" );
KeyValues::AutoDelete autodelete( kvUpdate );
kvUpdate->SetInt( "update/members/numSlots", numGameSlots );
m_pMatchSession->UpdateSessionSettings( kvUpdate );
}
m_eState = STATE_FINISHED;
}
}

167
matchmaking/ds_searcher.h Normal file
View File

@@ -0,0 +1,167 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef DS_SEARCHER_H
#define DS_SEARCHER_H
#ifdef _WIN32
#pragma once
#endif
class CDsSearcher;
#include "mm_framework.h"
class CDsSearcher : public IMatchAsyncOperationCallback, IMatchEventsSink
{
public:
explicit CDsSearcher( KeyValues *pSettings, uint64 uiReserveCookie, IMatchSession *pMatchSession, uint64 ullCrypt = 0ull );
virtual ~CDsSearcher();
// IMatchEventsSink
public:
virtual void OnEvent( KeyValues *pEvent );
public:
virtual void Update();
virtual void Destroy();
virtual bool IsFinished();
struct DsResult_t
{
bool m_bAborted;
bool m_bDedicated;
char m_szConnectionString[256];
#ifdef _X360
char m_szInsecureSendableServerAddress[256];
#elif !defined( NO_STEAM )
char m_szPublicConnectionString[256];
char m_szPrivateConnectionString[256];
#endif
void CopyToServerKey( KeyValues *pKvServer, uint64 ullCrypt = 0ull ) const;
};
virtual DsResult_t const& GetResult();
protected:
void InitDedicatedSearch();
void InitWithKnownServer();
void ReserveNextServer();
virtual void OnOperationFinished( IMatchAsyncOperation *pOperation );
float m_flTimeout;
IMatchAsyncOperation *m_pAsyncOperation;
protected:
#ifdef _X360
CXlspTitleServers *m_pTitleServers;
void Xlsp_EnumerateDcs();
void Xlsp_OnEnumerateDcsCompleted();
CUtlVector< CXlspDatacenter > m_arrDatacenters;
char m_chDatacenterQuery[ MAX_PATH ];
void Xlsp_PrepareDatacenterQuery();
void Xlsp_StartNextDc();
CXlspDatacenter m_dc;
void Xlsp_OnDcServerBatch( void const *pData, int numBytes );
CUtlVector< uint16 > m_arrServerPorts;
#elif !defined( NO_STEAM )
int m_nSearchPass;
void Steam_SearchPass();
class CServerListListener : public ISteamMatchmakingServerListResponse
{
public:
explicit CServerListListener( CDsSearcher *pDsSearcher, CUtlVector< MatchMakingKeyValuePair_t > &filters );
void Destroy();
public:
// Server has responded ok with updated data
virtual void ServerResponded( HServerListRequest hReq, int iServer );
// Server has failed to respond
virtual void ServerFailedToRespond( HServerListRequest hReq, int iServer )
{
gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers()
->GetServerDetails( hReq, iServer );
DevMsg( " server failed to respond '%s'\n",
pServer->m_NetAdr.GetConnectionAddressString() );
}
// A list refresh you had initiated is now 100% completed
virtual void RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response );
protected:
CDsSearcher *m_pOuter;
HServerListRequest m_hRequest;
};
friend class CServerListListener;
CServerListListener *m_pServerListListener;
struct DsServer_t
{
DsServer_t( char const *szConnectionString, char const *szPrivateConnectionString, int nPing ) : m_nPing( nPing )
{
Q_strncpy( m_szConnectionString, szConnectionString, ARRAYSIZE( m_szConnectionString ) );
Q_strncpy( m_szPrivateConnectionString, szPrivateConnectionString, ARRAYSIZE( m_szPrivateConnectionString ) );
}
char m_szConnectionString[256];
char m_szPrivateConnectionString[256];
int m_nPing;
};
CUtlVector< DsServer_t > m_arrServerList;
void Steam_OnDedicatedServerListFetched();
#endif
protected:
IMatchSession *m_pMatchSession;
KeyValues *m_pSettings;
uint64 m_uiReserveCookie;
KeyValues *m_pReserveSettings;
KeyValues::AutoDelete m_autodelete_pReserveSettings;
enum State_t
{
STATE_INIT,
STATE_WAITING,
#ifdef _X360
STATE_XLSP_ENUMERATE_DCS,
STATE_XLSP_NEXT_DC,
STATE_XLSP_REQUESTING_SERVERS,
#elif !defined( NO_STEAM )
STATE_STEAM_REQUESTING_SERVERS,
STATE_STEAM_NEXT_SEARCH_PASS,
#endif
STATE_RESERVING,
STATE_FINISHED,
};
State_t m_eState;
DsResult_t m_Result;
// If we are loadtesting, do a 2-pass search
// 1st pass, look for empty ds with sv_load_test = 1
// 2 pass, don't include sv_load_test flag in search
bool m_bLoadTest;
uint64 m_ullCrypt;
};
#endif

View File

@@ -0,0 +1,351 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "mm_title_richpresence.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CMatchTitle::CMatchTitle()
{
;
}
CMatchTitle::~CMatchTitle()
{
;
}
//
// Init / shutdown
//
InitReturnVal_t CMatchTitle::Init()
{
if ( IGameEventManager2 *mgr = g_pMatchExtensions->GetIGameEventManager2() )
{
mgr->AddListener( this, "server_pre_shutdown", false );
mgr->AddListener( this, "game_newmap", false );
mgr->AddListener( this, "finale_start", false );
mgr->AddListener( this, "round_start", false );
mgr->AddListener( this, "round_end", false );
mgr->AddListener( this, "difficulty_changed", false );
}
return INIT_OK;
}
void CMatchTitle::Shutdown()
{
if ( IGameEventManager2 *mgr = g_pMatchExtensions->GetIGameEventManager2() )
{
mgr->RemoveListener( this );
}
}
//
// Implementation
//
uint64 CMatchTitle::GetTitleID()
{
#ifdef _X360
return 0xFFFFFFFFFFFFFFFF;
#elif !defined( SWDS )
static uint64 uiAppID = 0ull;
if ( !uiAppID && steamapicontext && steamapicontext->SteamUtils() )
{
uiAppID = steamapicontext->SteamUtils()->GetAppID();
}
return uiAppID;
#else
return 0ull;
#endif
}
uint64 CMatchTitle::GetTitleServiceID()
{
#ifdef _X360
return 0x45410880ull; // Left 4 Dead 1 Service ID
#else
return 0ull;
#endif
}
bool CMatchTitle::IsMultiplayer()
{
return true;
}
void CMatchTitle::PrepareNetStartupParams( void *pNetStartupParams )
{
#ifdef _X360
XNetStartupParams &xnsp = *( XNetStartupParams * ) pNetStartupParams;
xnsp.cfgQosDataLimitDiv4 = 64; // 256 bytes
xnsp.cfgSockDefaultRecvBufsizeInK = 64; // Increase receive size for UDP to 64k
xnsp.cfgSockDefaultSendBufsizeInK = 64; // Keep send size at 64k too
int numGamePlayersMax = GetTotalNumPlayersSupported();
int numConnections = 4 * ( numGamePlayersMax - 1 );
// - the max number of connections to members of your game party
// - the max number of connections to members of your social party
// - the max number of connections to a pending game party (if you are joining a new one ).
// - matchmakings client info structure also creates a connection per client for the lobby.
// 1 - the main game session
int numTotalConnections = 1 + numConnections;
// 29 - total Connections (XNADDR/XNKID pairs) ,using 5 sessions (XNKID/XNKEY pairs).
xnsp.cfgKeyRegMax = 16; //adding some extra room because of lazy dealocation of these pairs.
xnsp.cfgSecRegMax = MAX( 64, numTotalConnections ); //adding some extra room because of lazy dealocation of these pairs.
xnsp.cfgSockMaxDgramSockets = xnsp.cfgSecRegMax;
xnsp.cfgSockMaxStreamSockets = xnsp.cfgSecRegMax;
#endif
}
int CMatchTitle::GetTotalNumPlayersSupported()
{
return 1;
}
// Get a guest player name
char const * CMatchTitle::GetGuestPlayerName( int iUserIndex )
{
if ( vgui::ILocalize *pLocalize = g_pMatchExtensions->GetILocalize() )
{
if ( wchar_t* wStringTableEntry = pLocalize->Find( "#L4D360UI_Character_Guest" ) )
{
static char szName[ MAX_PLAYER_NAME_LENGTH ] = {0};
pLocalize->ConvertUnicodeToANSI( wStringTableEntry, szName, ARRAYSIZE( szName ) );
return szName;
}
}
return "";
}
// Sets up all necessary client-side convars and user info before
// connecting to server
void CMatchTitle::PrepareClientForConnect( KeyValues *pSettings )
{
#ifndef SWDS
int numPlayers = 1;
#ifdef _X360
numPlayers = XBX_GetNumGameUsers();
#endif
//
// Now we set the convars
//
for ( int k = 0; k < numPlayers; ++ k )
{
int iController = k;
#ifdef _X360
iController = XBX_GetUserId( k );
#endif
IPlayerLocal *pPlayerLocal = g_pPlayerManager->GetLocalPlayer( iController );
if ( !pPlayerLocal )
continue;
// Set "name"
static SplitScreenConVarRef s_cl_name( "name" );
char const *szName = pPlayerLocal->GetName();
s_cl_name.SetValue( k, szName );
// Set "networkid_force"
if ( IsX360() )
{
static SplitScreenConVarRef s_networkid_force( "networkid_force" );
uint64 xid = pPlayerLocal->GetXUID();
s_networkid_force.SetValue( k, CFmtStr( "%08X:%08X:", uint32( xid >> 32 ), uint32( xid ) ) );
}
}
#endif
}
bool CMatchTitle::StartServerMap( KeyValues *pSettings )
{
int numPlayers = 1;
#ifdef _X360
numPlayers = XBX_GetNumGameUsers();
#endif
char const *szMap = pSettings->GetString( "game/bspname", NULL );
if ( !szMap )
return false;
// Check that we have the server interface and that the map is valid
if ( !g_pMatchExtensions->GetIVEngineServer() )
return false;
if ( !g_pMatchExtensions->GetIVEngineServer()->IsMapValid( szMap ) )
return false;
//
// Prepare game dll reservation package
//
KeyValues *pGameDllReserve = g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForReservation( pSettings );
KeyValues::AutoDelete autodelete( pGameDllReserve );
pGameDllReserve->SetString( "map/mapcommand", ( numPlayers <= 1 ) ? "map" : "ss_map" );
if ( !Q_stricmp( "commentary", pSettings->GetString( "options/play", "" ) ) )
pGameDllReserve->SetString( "map/mapcommand", "map_commentary" );
// Run map based off the faked reservation packet
g_pMatchExtensions->GetIServerGameDLL()->ApplyGameSettings( pGameDllReserve );
return true;
}
static KeyValues * GetCurrentMatchSessionSettings()
{
IMatchSession *pIMatchSession = g_pMatchFramework->GetMatchSession();
return pIMatchSession ? pIMatchSession->GetSessionSettings() : NULL;
}
void CMatchTitle::OnEvent( KeyValues *pEvent )
{
char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "OnPlayerRemoved", szEvent ) ||
!Q_stricmp( "OnPlayerUpdated", szEvent ) )
{
MM_Title_RichPresence_PlayersChanged( GetCurrentMatchSessionSettings() );
}
else if ( !Q_stricmp( "OnMatchSessionUpdate", szEvent ) )
{
if ( !Q_stricmp( pEvent->GetString( "state" ), "updated" ) )
{
if ( KeyValues *kvUpdate = pEvent->FindKey( "update" ) )
{
MM_Title_RichPresence_Update( GetCurrentMatchSessionSettings(), kvUpdate );
}
}
else if ( !Q_stricmp( pEvent->GetString( "state" ), "created" ) ||
!Q_stricmp( pEvent->GetString( "state" ), "ready" ) )
{
MM_Title_RichPresence_Update( GetCurrentMatchSessionSettings(), NULL );
}
else if ( !Q_stricmp( pEvent->GetString( "state" ), "closed" ) )
{
MM_Title_RichPresence_Update( NULL, NULL );
}
}
}
//
//
//
int CMatchTitle::GetEventDebugID( void )
{
return EVENT_DEBUG_ID_INIT;
}
void CMatchTitle::FireGameEvent( IGameEvent *pIGameEvent )
{
// Check if the current match session is on an active game server
IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession();
if ( !pMatchSession )
return;
KeyValues *pSessionSettings = pMatchSession->GetSessionSettings();
char const *szGameServer = pSessionSettings->GetString( "server/server", "" );
char const *szSystemLock = pSessionSettings->GetString( "system/lock", "" );
if ( ( !szGameServer || !*szGameServer ) &&
( !szSystemLock || !*szSystemLock ) )
return;
// Also don't run on the client when there's a host
char const *szSessionType = pMatchSession->GetSessionSystemData()->GetString( "type", NULL );
if ( szSessionType && !Q_stricmp( szSessionType, "client" ) )
return;
// Parse the game event
char const *szGameEvent = pIGameEvent->GetName();
if ( !szGameEvent || !*szGameEvent )
return;
if ( !Q_stricmp( "round_start", szGameEvent ) )
{
pMatchSession->UpdateSessionSettings( KeyValues::AutoDeleteInline( KeyValues::FromString(
"update",
" update { "
" game { "
" state game "
" } "
" } "
) ) );
}
else if ( !Q_stricmp( "finale_start", szGameEvent ) )
{
pMatchSession->UpdateSessionSettings( KeyValues::AutoDeleteInline( KeyValues::FromString(
"update",
" update { "
" game { "
" state finale "
" } "
" } "
) ) );
}
else if ( !Q_stricmp( "game_newmap", szGameEvent ) )
{
KeyValues *kvUpdate = KeyValues::FromString(
"update",
" update { "
" game { "
" state game "
" } "
" } "
);
KeyValues::AutoDelete autodelete( kvUpdate );
pMatchSession->UpdateSessionSettings( kvUpdate );
}
else if ( !Q_stricmp( "difficulty_changed", szGameEvent ) )
{
char const *szDifficulty = pIGameEvent->GetString( "strDifficulty", "normal" );
KeyValues *kvUpdate = KeyValues::FromString(
"update",
" update { "
" game { "
" difficulty = "
" } "
" } "
);
KeyValues::AutoDelete autodelete( kvUpdate );
kvUpdate->SetString( "update/game/difficulty", szDifficulty );
pMatchSession->UpdateSessionSettings( kvUpdate );
}
else if ( !Q_stricmp( "server_pre_shutdown", szGameEvent ) )
{
char const *szReason = pIGameEvent->GetString( "reason", "quit" );
if ( !Q_stricmp( szReason, "quit" ) )
{
DevMsg( "Received server_pre_shutdown notification - server is shutting down...\n" );
// Transform the server shutdown event into game end event
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnEngineDisconnectReason", "reason", "Server shutting down"
) );
}
}
}

View File

@@ -0,0 +1,83 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_TITLE_H
#define MM_TITLE_H
#ifdef _WIN32
#pragma once
#endif
#include "../mm_framework.h"
class CMatchTitle :
public IMatchTitle,
public IMatchEventsSink,
public IGameEventListener2
{
// Methods exposed for the framework
public:
virtual InitReturnVal_t Init();
virtual void Shutdown();
// Methods of IMatchTitle
public:
// Title ID
virtual uint64 GetTitleID();
// Service ID for XLSP
virtual uint64 GetTitleServiceID();
// Whether we are a single-player title or multi-player title
virtual bool IsMultiplayer();
// Prepare network startup params for the title
virtual void PrepareNetStartupParams( void *pNetStartupParams );
// Get total number of players supported by the title
virtual int GetTotalNumPlayersSupported();
// Get a guest player name
virtual char const * GetGuestPlayerName( int iUserIndex );
// Decipher title data fields
virtual TitleDataFieldsDescription_t const * DescribeTitleDataStorage();
// Title achievements
virtual TitleAchievementsDescription_t const * DescribeTitleAchievements();
// Title avatar awards
virtual TitleAvatarAwardsDescription_t const * DescribeTitleAvatarAwards();
// Title leaderboards
virtual KeyValues * DescribeTitleLeaderboard( char const *szLeaderboardView );
// Sets up all necessary client-side convars and user info before
// connecting to server
virtual void PrepareClientForConnect( KeyValues *pSettings );
// Start up a listen server with the given settings
virtual bool StartServerMap( KeyValues *pSettings );
// Methods of IMatchEventsSink
public:
virtual void OnEvent( KeyValues *pEvent );
// Methods of IGameEventListener2
public:
// FireEvent is called by EventManager if event just occured
// KeyValue memory will be freed by manager if not needed anymore
virtual void FireGameEvent( IGameEvent *event );
virtual int GetEventDebugID( void );
public:
CMatchTitle();
~CMatchTitle();
};
// Match title singleton
extern CMatchTitle *g_pMatchTitle;
#endif // MM_TITLE_H

View File

@@ -0,0 +1,312 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "mm_title_richpresence.h"
#include "vstdlib/random.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
class CMatchTitleGameSettingsMgr : public IMatchTitleGameSettingsMgr
{
public:
// 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 );
// 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 );
// Initializes full game settings from potentially abbreviated game settings
virtual void InitializeGameSettings( KeyValues *pSettings );
// Extends game settings update packet before it gets merged with
// session settings and networked to remote clients
virtual void ExtendGameSettingsUpdateKeys( KeyValues *pSettings, KeyValues *pUpdateDeleteKeys );
// 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 );
};
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
//
{
pDetails->SetString( "game/bspname", CFmtStr( "%s", si.m_szMapName ) );
}
}
// 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 { "
" bspname #empty# "
" } "
);
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" );
}
// Rolls up game details for matches grouping
KeyValues * CMatchTitleGameSettingsMgr::RollupGameDetails( KeyValues *pDetails, KeyValues *pRollup, KeyValues *pQuery )
{
return NULL;
}
// Defines dedicated server search key
KeyValues * CMatchTitleGameSettingsMgr::DefineDedicatedSearchKeys( KeyValues *pSettings )
{
if ( IsPC() )
{
static ConVarRef sv_search_key( "sv_search_key" );
KeyValues *pKeys = new KeyValues( "SearchKeys" );
pKeys->SetString( "gametype", CFmtStr( "%s,sv_search_key_%s%d",
"empty",
sv_search_key.GetString(),
g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() ) );
return pKeys;
}
else
{
return NULL;
}
}
// Defines session search keys for matchmaking
KeyValues * CMatchTitleGameSettingsMgr::DefineSessionSearchKeys( KeyValues *pSettings )
{
MEM_ALLOC_CREDIT();
KeyValues *pResult = new KeyValues( "SessionSearch" );
pResult->SetInt( "numPlayers", pSettings->GetInt( "members/numPlayers", XBX_GetNumGameUsers() ) );
/*
char const *szGameMode = pSettings->GetString( "game/mode", "" );
if ( IsX360() )
{
if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
{
static ContextValue_t values[] = {
{ "versus", SESSION_MATCH_QUERY_PUBLIC_STATE_C___SORT___CHAPTER },
{ "teamversus", SESSION_MATCH_QUERY_TEAM_STATE_C_CHAPTER },
{ "scavenge", SESSION_MATCH_QUERY_PUBLIC_STATE_C_CHAPTER___SORT___ROUNDS },
{ "teamscavenge", SESSION_MATCH_QUERY_TEAM_STATE_C_CHAPTER_ROUNDS },
{ "survival", SESSION_MATCH_QUERY_PUBLIC_STATE_C_CHAPTER },
{ "coop", SESSION_MATCH_QUERY_PUBLIC_STATE_C_DIFF___SORT___CHAPTER },
{ "realism", SESSION_MATCH_QUERY_PUBLIC_STATE_C_DIFF___SORT___CHAPTER },
{ NULL, 0xFFFF },
};
pResult->SetInt( "rule", values->ScanValues( szValue ) );
}
// Set the matchmaking version
pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_MMVERSION ), mm_matchmaking_version.GetInt() );
#ifdef _X360
// Set the installed DLCs masks
uint64 uiDlcsMask = MatchSession_GetDlcInstalledMask();
for ( int k = 1; k <= mm_matchmaking_dlcsquery.GetInt(); ++ k )
{
pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_MMVERSION + k ), !!( uiDlcsMask & ( 1ull << k ) ) );
}
pResult->SetInt( "dlc1", PROPERTY_MMVERSION + 1 );
pResult->SetInt( "dlcN", PROPERTY_MMVERSION + mm_matchmaking_dlcsquery.GetInt() );
#endif
// X_CONTEXT_GAME_TYPE
pResult->SetInt( CFmtStr( "Contexts/%d", X_CONTEXT_GAME_TYPE ), X_CONTEXT_GAME_TYPE_STANDARD );
// X_CONTEXT_GAME_MODE
if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
{
pResult->SetInt( CFmtStr( "Contexts/%d", X_CONTEXT_GAME_MODE ), g_pcv_CONTEXT_GAME_MODE->ScanValues( szValue ) );
}
if ( char const *szValue = pSettings->GetString( "game/state", NULL ) )
{
pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_STATE ), g_pcv_CONTEXT_STATE->ScanValues( szValue ) );
}
if ( char const *szValue = pSettings->GetString( "game/difficulty", NULL ) )
{
if ( !Q_stricmp( "coop", szGameMode ) || !Q_stricmp( "realism", szGameMode ) )
{
pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_DIFFICULTY ), g_pcv_CONTEXT_DIFFICULTY->ScanValues( szValue ) );
}
}
if ( int val = pSettings->GetInt( "game/maxrounds" ) )
{
if ( !Q_stricmp( "scavenge", szGameMode ) || !Q_stricmp( "teamscavenge", szGameMode ) )
{
pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_MAXROUNDS ), val );
}
}
char const *szCampaign = pSettings->GetString( "game/campaign" );
if ( *szCampaign )
{
DWORD dwContext = CONTEXT_CAMPAIGN_UNKNOWN;
if ( KeyValues *pAllMissions = g_pMatchExtL4D->GetAllMissions() )
{
if ( KeyValues *pMission = pAllMissions->FindKey( szCampaign ) )
{
dwContext = pMission->GetInt( "x360ctx", dwContext );
}
}
if ( dwContext != CONTEXT_CAMPAIGN_UNKNOWN )
{
pResult->SetInt( CFmtStr( "Contexts/%d", CONTEXT_CAMPAIGN ), dwContext );
}
}
if ( int val = pSettings->GetInt( "game/chapter" ) )
{
pResult->SetInt( CFmtStr( "Properties/%d", PROPERTY_CHAPTER ), val );
}
}
else
*/
{
if ( char const *szValue = pSettings->GetString( "game/bspname", NULL ) )
{
pResult->SetString( "Filter=/game:bspname", szValue );
}
}
return pResult;
}
// Initializes full game settings from potentially abbreviated game settings
void CMatchTitleGameSettingsMgr::InitializeGameSettings( KeyValues *pSettings )
{
char const *szNetwork = pSettings->GetString( "system/network", "LIVE" );
if ( KeyValues *kv = pSettings->FindKey( "game", true ) )
{
kv->SetString( "state", "lobby" );
}
// Offline games don't need slots and player setup
if ( !Q_stricmp( "offline", szNetwork ) )
return;
//
// Set the number of slots
//
int numSlots = 2;
pSettings->SetInt( "members/numSlots", numSlots );
}
// Extends game settings update packet before it gets merged with
// session settings and networked to remote clients
void CMatchTitleGameSettingsMgr::ExtendGameSettingsUpdateKeys( KeyValues *pSettings, KeyValues *pUpdateDeleteKeys )
{
}
// Prepares system for session creation
KeyValues * CMatchTitleGameSettingsMgr::PrepareForSessionCreate( KeyValues *pSettings )
{
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;
}
// 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();
}

View File

@@ -0,0 +1,39 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "../mm_title_main.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
LINK_MATCHMAKING_LIB();
static CMatchTitle g_MatchTitle;
CMatchTitle *g_pMatchTitle = &g_MatchTitle;
IMatchTitle *g_pIMatchTitle = g_pMatchTitle;
IMatchEventsSink *g_pIMatchTitleEventsSink = g_pMatchTitle;
//
// Init / shutdown
//
InitReturnVal_t MM_Title_Init()
{
return g_pMatchTitle->Init();
}
void MM_Title_Shutdown()
{
if ( g_pMatchTitle )
g_pMatchTitle->Shutdown();
}

View File

@@ -0,0 +1,52 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title_richpresence.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static void SetAllUsersContext( DWORD dwContextId, DWORD dwValue, bool bAsync = true )
{
#ifdef _X360
for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k )
{
if ( XBX_GetUserIsGuest( k ) )
continue;
int iCtrlr = XBX_GetUserId( k );
if ( bAsync )
XUserSetContextEx( iCtrlr, dwContextId, dwValue, MMX360_NewOverlappedDormant() );
else
XUserSetContext( iCtrlr, dwContextId, dwValue );
}
#endif
}
static void SetAllUsersProperty( DWORD dwPropertyId, DWORD cbValue, void const *pvValue )
{
#ifdef _X360
for ( int k = 0; k < ( int ) XBX_GetNumGameUsers(); ++ k )
{
if ( XBX_GetUserIsGuest( k ) )
continue;
int iCtrlr = XBX_GetUserId( k );
XUserSetPropertyEx( iCtrlr, dwPropertyId, cbValue, pvValue, MMX360_NewOverlappedDormant() );
}
#endif
}
KeyValues * MM_Title_RichPresence_PrepareForSessionCreate( KeyValues *pSettings )
{
return NULL;
}
void MM_Title_RichPresence_Update( KeyValues *pFullSettings, KeyValues *pUpdatedSettings )
{
}
void MM_Title_RichPresence_PlayersChanged( KeyValues *pFullSettings )
{
}

View File

@@ -0,0 +1,20 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_TITLE_RICHPRESENCE_H
#define MM_TITLE_RICHPRESENCE_H
#ifdef _WIN32
#pragma once
#endif
#include "mm_title.h"
void MM_Title_RichPresence_Update( KeyValues *pFullSettings, KeyValues *pUpdatedSettings );
void MM_Title_RichPresence_PlayersChanged( KeyValues *pFullSettings );
KeyValues * MM_Title_RichPresence_PrepareForSessionCreate( KeyValues *pSettings );
#endif // MM_TITLE_H

View File

@@ -0,0 +1,113 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
TitleDataFieldsDescription_t const * CMatchTitle::DescribeTitleDataStorage()
{
#define TD_ENTRY( szName, nTD, eDataType, numBytesOffset ) \
{ szName, TitleDataFieldsDescription_t::nTD, TitleDataFieldsDescription_t::eDataType, numBytesOffset }
static TitleDataFieldsDescription_t tdfd[] =
{
#if 0
TD_ENTRY( "TD1.Easy.Games.Total", DB_TD1, DT_U64, offsetof( TitleData1, mGames[0] ) ),
TD_ENTRY( "TD1.Normal.Games.Total", DB_TD1, DT_U64, offsetof( TitleData1, mGames[1] ) ),
TD_ENTRY( "TD1.Advanced.Games.Total", DB_TD1, DT_U64, offsetof( TitleData1, mGames[2] ) ),
TD_ENTRY( "TD1.Expert.Games.Total", DB_TD1, DT_U64, offsetof( TitleData1, mGames[3] ) ),
#endif
TD_ENTRY( NULL, DB_TD1, DT_U8, 0 )
};
#undef TD_ENTRY
return tdfd;
}
TitleAchievementsDescription_t const * CMatchTitle::DescribeTitleAchievements()
{
static TitleAchievementsDescription_t tad[] =
{
//#include "left4dead2.xhelp.achtitledesc.txt"
// END MARKER
{ NULL, 0 }
};
return tad;
}
TitleAvatarAwardsDescription_t const * CMatchTitle::DescribeTitleAvatarAwards()
{
static TitleAvatarAwardsDescription_t taad[] =
{
//#include "left4dead2.xhelp.avawtitledesc.txt"
// END MARKER
{ NULL, 0 }
};
return taad;
}
// Title leaderboards
KeyValues * CMatchTitle::DescribeTitleLeaderboard( char const *szLeaderboardView )
{
/*
// Check if this is a survival leaderboard
if ( char const *szSurvivalMap = StringAfterPrefix( szLeaderboardView, "survival_" ) )
{
if ( IsX360() )
{
// Find the corresponding record in the mission script
KeyValues *pSettings = new KeyValues( "settings" );
KeyValues::AutoDelete autodelete_pSettings( pSettings );
pSettings->SetString( "game/mode", "survival" );
KeyValues *pMissionInfo = NULL;
KeyValues *pMapInfo = g_pMatchExtL4D->GetMapInfoByBspName( pSettings, szSurvivalMap, &pMissionInfo );
if ( !pMapInfo || !pMissionInfo )
return NULL;
// Find the leaderboard description in the map info
KeyValues *pLbDesc = pMapInfo->FindKey( "x360leaderboard" );
if ( !pLbDesc )
return NULL;
// Insert the required keys
pLbDesc = pLbDesc->MakeCopy();
static KeyValues *s_pRatingKey = KeyValues::FromString( ":rating", // X360 leaderboards are rated
" name besttime " // game name of the rating field is "besttime"
" type uint64 " // type is uint64
" rule max" // rated field must be greater than cached value so that it can be written
);
pLbDesc->AddSubKey( s_pRatingKey->MakeCopy() );
pLbDesc->SetString( "besttime/type", "uint64" );
return pLbDesc;
}
if ( IsPC() )
{
KeyValues *pSettings = KeyValues::FromString( "SteamLeaderboard",
" :score besttime " // :score is the leaderboard value mapped to game name "besttime"
);
pSettings->SetInt( ":sort", k_ELeaderboardSortMethodDescending ); // Sort order when fetching and displaying leaderboard data
pSettings->SetInt( ":format", k_ELeaderboardDisplayTypeTimeMilliSeconds ); // Note: this is actually 1/100th seconds type, Steam change pending
pSettings->SetInt( ":upload", k_ELeaderboardUploadScoreMethodKeepBest ); // Upload method when writing to leaderboard
return pSettings;
}
}
*/
return NULL;
}

View File

@@ -0,0 +1,53 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "extkeyvalues.h"
#include <ctype.h>
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//
// Rule evaluation
//
static class CPropertyRule_Max : public IPropertyRule
{
public:
virtual bool ApplyRuleUint64( uint64 uiBase, uint64 &uiNew )
{
bool bResult = ( uiNew > uiBase );
uiNew = MAX( uiNew, uiBase );
return bResult;
}
virtual bool ApplyRuleFloat( float flBase, float &flNew )
{
bool bResult = ( flNew > flBase );
flNew = MAX( flNew, flBase );
return bResult;
}
}
g_PropertyRule_Max;
static class CPropertyRule_None : public IPropertyRule
{
public:
virtual bool ApplyRuleUint64( uint64 uiBase, uint64 &uiNew ) { return true; }
virtual bool ApplyRuleFloat( float flBase, float &flNew ) { return true; }
}
g_PropertyRule_None;
IPropertyRule * GetRuleByName( char const *szRuleName )
{
if ( !Q_stricmp( szRuleName, "max" ) )
return &g_PropertyRule_Max;
return &g_PropertyRule_None;
}

View File

@@ -0,0 +1,68 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef EXT_KEYVALUES_H
#define EXT_KEYVALUES_H
#ifdef _WIN32
#pragma once
#endif
#include "mm_framework.h"
#include "matchmaking/mm_helpers.h"
#define KeyValuesAddDefaultValue( kv, szKeyName, defValue, fnSet ) \
( (kv)->FindKey( szKeyName ) ? (1) : ( (kv)->fnSet( szKeyName, defValue ), 1 ) )
#define KeyValuesAddDefaultString( kv, szKeyName, defValue ) \
KeyValuesAddDefaultValue( kv, szKeyName, defValue, SetString )
//
// ContextValue_t allows for quick mapping from strings to title-defined values
// in title specific code.
//
// static ContextValue_t values[] = {
// { "=INGAME", CONTEXT_GAME_STATE_INGAME },
// { "=INFINALE", CONTEXT_GAME_STATE_INFINALE },
// { NULL, CONTEXT_GAME_STATE_INLOBBY },
// };
// SetAllUsersContext( CONTEXT_GAME_STATE, values->ScanValues( szValue ) );
struct ContextValue_t
{
char const *m_szValue;
unsigned int m_dwValue;
inline unsigned int ScanValues( char const *szValue )
{
ContextValue_t const *p = this;
for ( ; p->m_szValue; ++ p )
{
if ( !Q_stricmp( p->m_szValue, szValue ) )
break;
}
return p->m_dwValue;
}
};
//
// Rule evaluation
//
class IPropertyRule
{
public:
virtual bool ApplyRuleUint64( uint64 uiBase, uint64 &uiNew ) = 0;
virtual bool ApplyRuleFloat( float flBase, float &flNew ) = 0;
};
IPropertyRule * GetRuleByName( char const *szRuleName );
#endif

169
matchmaking/game.cpp Normal file
View File

@@ -0,0 +1,169 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "game.h"
#include "gamemanager.h"
#include "matchsystem.h"
#include "playermanager.h"
#include "matchmaking.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
CMatchGame::CMatchGame()
{
}
CMatchGame::~CMatchGame()
{
}
void CMatchGame::Reset()
{
RemoveAllPlayers();
}
XNKID CMatchGame::GetXNKID()
{
return (XNKID) g_pMatchmaking->GetSessionXNKID();
}
int CMatchGame::GetNumPlayers()
{
return m_PlayerList.Count();
}
IPlayer * CMatchGame::GetPlayer( int index )
{
if ( m_PlayerList.IsValidIndex( index ) )
return m_PlayerList[index];
else
return NULL;
}
void CMatchGame::AddPlayer( XUID xuid, int pExperience[ MATCH_MAX_DIFFICULTIES ] /*= NULL*/, int pSkills[ MATCH_MAX_DIFFICULTIES ][ MATCH_MAX_SKILL_FIELDS ] /*= NULL*/ )
{
if ( MM_IsDebug() )
{
Msg( "[L4DMM] CMatchGame::AddPlayer( %llx )\n", xuid );
}
IPlayer * player = g_pPlayerManager->FindPlayer( xuid );
if ( !player )
{
Warning( "[L4DMM] CMatchGame::AddPlayer - Cannot find player by %llx!\n", xuid );
return;
}
// Find a player first
for ( int k = 0; k < m_PlayerList.Count(); ++ k )
{
if( m_PlayerList[k]->GetXUID() == xuid )
{
Warning( "[L4DMM] CMatchGame::AddPlayer - player %llx already added!\n", xuid );
return;
}
}
m_PlayerList.AddToTail( player );
}
void CMatchGame::RemovePlayer( XUID xuid )
{
if ( MM_IsDebug() )
{
Msg( "[L4DMM] CMatchGame::RemovePlayer( %llx )\n", xuid );
}
int numRemoved = 0;
for ( int k = 0; k < m_PlayerList.Count(); ++ k )
{
if( m_PlayerList[k]->GetXUID() == xuid )
{
m_PlayerList.Remove( k );
++ numRemoved;
}
}
if ( !numRemoved )
{
Warning( "[L4DMM] CMatchGame::RemovePlayer - Cannot find player by %llx!\n", xuid );
}
}
void CMatchGame::RemoveAllPlayers( )
{
if ( MM_IsDebug() )
{
Msg( "[L4DMM] CMatchGame::RemoveAllPlayers\n" );
}
m_PlayerList.RemoveAll();
}
int CMatchGame::GetAggregateExperience( )
{
int difficulty = 1; // CMatchSystem::GetMatchSystem()->GetManager()->GameGetDifficulty();
double exp = 0;
int numExp = 0;
int numPlayers = m_PlayerList.Count();
for( int index = 0; index < numPlayers; ++index )
{
IPlayer * player = GetPlayer( index );
if( player )
{
// TODO: exp += player->GetExperience( difficulty );
++ numExp;
}
}
if( numExp )
exp /= numExp;
if ( MM_IsDebug() >= 2 )
{
Msg( "[L4DMM] Game aggregate experience on difficulty%d = %f out of %d exp players.\n", difficulty, exp, numExp );
}
return exp;
}
int CMatchGame::GetAggregateSkill( int iSkillType )
{
int difficulty = 1; // CMatchSystem::GetMatchSystem()->GetManager()->GameGetDifficulty();
double skill = 0;
int numSkills = 0;
int numPlayers = m_PlayerList.Count();
for( int index = 0; index < numPlayers; ++index )
{
IPlayer * player = GetPlayer( index );
if( player )
{
// TODO: skill += player->GetSkill( iSkillType, difficulty );
++ numSkills;
}
}
if( numSkills )
skill /= numSkills;
if ( MM_IsDebug() >= 2 )
{
Msg( "[L4DMM] Game aggregate skill%d on difficulty%d = %f out of %d skill players.\n", iSkillType, difficulty, skill, numSkills );
}
return skill;
}

50
matchmaking/game.h Normal file
View File

@@ -0,0 +1,50 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef _GAME_H_
#define _GAME_H_
#include "mm_framework.h"
#include "matchmaking/igame.h"
#include "utlvector.h"
//In an idea world game is manageing all of the aspects of the game session, however in our case
//it will serve as an interface into the engine and matchmaking to get the information we need.
class CMatchGame : public IMatchGame
{
public:
CMatchGame();
~CMatchGame();
//IMatchGame
virtual void Reset();
virtual XNKID GetXNKID();
virtual int GetNumPlayers();
virtual IPlayer * GetPlayer(int index);
virtual void AddPlayer( XUID xuid, int pExperience[ MATCH_MAX_DIFFICULTIES ] = NULL, int pSkills[ MATCH_MAX_DIFFICULTIES ][ MATCH_MAX_SKILL_FIELDS ] = NULL );
virtual void RemovePlayer( XUID xuid );
virtual void RemoveAllPlayers( );
virtual int GetAggregateExperience( );
virtual int GetAggregateSkill( int iSkillType );
private:
typedef CUtlVector< IPlayer * > IPlayerList_t;
IPlayerList_t m_PlayerList;
};
#endif

View File

@@ -0,0 +1,540 @@
//========= Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#include "mm_framework.h"
#include "leaderboards.h"
#include "fmtstr.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
//
// Definition of leaderboard request queue class
//
class CLeaderboardRequestQueue : public ILeaderboardRequestQueue
{
public:
CLeaderboardRequestQueue();
~CLeaderboardRequestQueue();
// ILeaderboardRequestQueue
public:
virtual void Request( KeyValues *pRequest );
virtual void Update();
public:
KeyValues * GetFinishedRequest();
protected:
CUtlVector< KeyValues * > m_arrRequests;
bool m_bQueryRunning;
KeyValues *m_pFinishedRequest;
void OnStartNewQuery();
void OnSubmitQuery();
void OnQueryFinished();
void Cleanup();
protected:
#ifdef _X360
// Leaderboard query data
CUtlVector< XUID > m_arrXuids;
CUtlVector< XUSER_STATS_SPEC > m_arrSpecs;
CUtlBuffer m_bufResults;
XOVERLAPPED m_xOverlapped;
void ProcessResults( XUSER_STATS_READ_RESULTS const *pResults );
// Leaderboard description data
CUtlVector< KeyValues * > m_arrViewDescriptions;
KeyValues * FindViewDescription( DWORD dwViewId );
#elif !defined( NO_STEAM )
KeyValues *m_pViewDescription;
CCallResult< CLeaderboardRequestQueue, LeaderboardFindResult_t > m_CallbackOnLeaderboardFindResult;
void Steam_OnLeaderboardFindResult( LeaderboardFindResult_t *p, bool bError );
CCallResult< CLeaderboardRequestQueue, LeaderboardScoresDownloaded_t > m_CallbackOnLeaderboardScoresDownloaded;
void Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError );
void ProcessResults( LeaderboardEntry_t const &lbe );
#endif
};
CLeaderboardRequestQueue g_LeaderboardRequestQueue;
ILeaderboardRequestQueue *g_pLeaderboardRequestQueue = &g_LeaderboardRequestQueue;
//
// Implementation of leaderboard request queue class
//
CLeaderboardRequestQueue::CLeaderboardRequestQueue() :
m_bQueryRunning( false ),
#if !defined( _X360 ) && !defined( NO_STEAM )
m_pViewDescription( NULL ),
#endif
m_pFinishedRequest( NULL )
{
}
CLeaderboardRequestQueue::~CLeaderboardRequestQueue()
{
Cleanup();
}
void CLeaderboardRequestQueue::Request( KeyValues *pRequest )
{
if ( !pRequest )
return;
DevMsg( "CLeaderboardRequestQueue::Request\n" );
KeyValuesDumpAsDevMsg( pRequest, 1 );
m_arrRequests.AddToTail( pRequest->MakeCopy() );
}
void CLeaderboardRequestQueue::Update()
{
if ( m_bQueryRunning )
{
#ifdef _X360
if ( XHasOverlappedIoCompleted( &m_xOverlapped ) )
{
OnQueryFinished();
}
#endif
}
else if ( m_arrRequests.Count() )
{
OnStartNewQuery();
}
else if ( m_arrRequests.NumAllocated() )
{
Cleanup();
}
}
KeyValues * CLeaderboardRequestQueue::GetFinishedRequest()
{
return m_pFinishedRequest;
}
void CLeaderboardRequestQueue::OnQueryFinished()
{
#ifdef _X360
// Submit results for processing
XUSER_STATS_READ_RESULTS const *pResults = ( XUSER_STATS_READ_RESULTS const * ) m_bufResults.Base();
if ( pResults && pResults->dwNumViews > 0 && pResults->pViews )
ProcessResults( pResults );
#endif
// Query is no longer running
m_bQueryRunning = false;
DevMsg( "CLeaderboardRequestQueue::OnQueryFinished\n" );
KeyValuesDumpAsDevMsg( m_pFinishedRequest, 1 );
// Stuff the data into the players
#ifdef _X360
for ( int iCtrlr = 0; iCtrlr < XUSER_MAX_COUNT; ++ iCtrlr )
{
XUID xuid = 0;
XUSER_SIGNIN_INFO xsi;
if ( XUserGetSigninState( iCtrlr ) != eXUserSigninState_NotSignedIn &&
ERROR_SUCCESS == XUserGetSigninInfo( iCtrlr, XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) &&
!(xsi.dwInfoFlags & XUSER_INFO_FLAG_GUEST) )
xuid = xsi.xuid;
#else
int iCtrlr = XBX_GetPrimaryUserId();
{
XUID xuid = g_pPlayerManager->GetLocalPlayer( iCtrlr )->GetXUID();
#endif
KeyValues *pUserViews = NULL;
if ( xuid )
pUserViews = m_pFinishedRequest->FindKey( CFmtStr( "%llx", xuid ) );
if ( pUserViews )
{
IPlayerLocal *pPlayer = g_pPlayerManager->GetLocalPlayer( iCtrlr );
if ( pPlayer )
(( PlayerLocal * ) pPlayer)->OnLeaderboardRequestFinished( pUserViews );
}
}
}
void CLeaderboardRequestQueue::Cleanup()
{
#ifdef _X360
// Clear view descriptions
while ( m_arrViewDescriptions.Count() )
{
m_arrViewDescriptions.Head()->deleteThis();
m_arrViewDescriptions.FastRemove( 0 );
}
m_arrXuids.Purge();
m_arrSpecs.Purge();
m_bufResults.Purge();
m_arrViewDescriptions.Purge();
#elif !defined( NO_STEAM )
if ( m_pViewDescription )
m_pViewDescription->deleteThis();
m_pViewDescription = NULL;
#endif
// Clear requests
while ( m_arrRequests.Count() )
{
m_arrRequests.Head()->deleteThis();
m_arrRequests.FastRemove( 0 );
}
m_arrRequests.Purge();
// Clear finished result
if ( m_pFinishedRequest )
m_pFinishedRequest->deleteThis();
m_pFinishedRequest = NULL;
}
void CLeaderboardRequestQueue::OnStartNewQuery()
{
// When we are starting a new query we need to get rid of the old finished result
if ( m_pFinishedRequest )
m_pFinishedRequest->deleteThis();
m_pFinishedRequest = NULL;
#if !defined( NO_STEAM ) && !defined( SWDS )
extern CInterlockedInt g_numSteamLeaderboardWriters;
if ( g_numSteamLeaderboardWriters )
return; // yield to writers that can alter the leaderboard
#endif
DevMsg( "CLeaderboardRequestQueue::OnStartNewQuery preparing request...\n" );
#ifdef _X360
// Prepare the XUIDs first
m_arrXuids.RemoveAll();
for ( DWORD k = 0; k < XBX_GetNumGameUsers(); ++ k )
{
int iCtrlr = XBX_GetUserId( k );
XUSER_SIGNIN_INFO xsi;
if ( XUserGetSigninState( iCtrlr ) != eXUserSigninState_NotSignedIn &&
ERROR_SUCCESS == XUserGetSigninInfo( iCtrlr, XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) &&
!(xsi.dwInfoFlags & XUSER_INFO_FLAG_GUEST) )
{
m_arrXuids.AddToTail( xsi.xuid );
DevMsg( " XUID: %s (%llx)\n", xsi.szUserName, xsi.xuid );
}
}
// Clear view descriptions
while ( m_arrViewDescriptions.Count() )
{
m_arrViewDescriptions.Head()->deleteThis();
m_arrViewDescriptions.FastRemove( 0 );
}
// Prepare the spec
m_arrSpecs.RemoveAll();
for ( int q = 0; q < m_arrRequests.Count(); ++ q )
{
KeyValues *pRequest = m_arrRequests[q];
KeyValues::AutoDelete autodelete_pRequest( pRequest );
m_arrRequests.Remove( q -- );
char const *szViewName = pRequest->GetName();
KeyValues *pDescription = g_pMMF->GetMatchTitle()->DescribeTitleLeaderboard( szViewName );
if ( !pDescription )
{
DevWarning( " View %s failed to allocate description!\n", szViewName );
}
KeyValues::AutoDelete autodelete_pDescription( pDescription );
// See if we already have a request for this view
DWORD dwViewId = pDescription->GetInt( ":id" );
for ( int k = 0; k < m_arrSpecs.Count(); ++ k )
{
if ( m_arrSpecs[k].dwViewId == dwViewId )
{
dwViewId = 0;
break;
}
}
// If we already have a request for this view, then continue
if ( !dwViewId )
continue;
// Otherwise add this view to the spec
XUSER_STATS_SPEC xss = {0};
xss.dwViewId = dwViewId;
Assert( !xss.dwNumColumnIds );
m_arrSpecs.AddToTail( xss );
pDescription->SetString( ":name", szViewName );
m_arrViewDescriptions.AddToTail( pDescription );
autodelete_pDescription.Assign( NULL ); // don't autodelete now
DevMsg( " View: %s (%d)\n", szViewName, dwViewId );
}
#elif !defined( NO_STEAM )
// Clear view descriptions
if ( m_pViewDescription )
m_pViewDescription->deleteThis();
m_pViewDescription = NULL;
for ( int q = 0; q < m_arrRequests.Count(); ++ q )
{
KeyValues *pRequest = m_arrRequests[q];
KeyValues::AutoDelete autodelete_pRequest( pRequest );
m_arrRequests.Remove( q -- );
char const *szViewName = pRequest->GetName();
m_pViewDescription = g_pMMF->GetMatchTitle()->DescribeTitleLeaderboard( szViewName );
if ( !m_pViewDescription )
{
DevWarning( " View %s failed to allocate description!\n", szViewName );
continue;
}
m_pViewDescription->SetString( ":name", szViewName );
SteamAPICall_t hCall = steamapicontext->SteamUserStats()->FindLeaderboard( szViewName );
m_CallbackOnLeaderboardFindResult.Set( hCall, this, &CLeaderboardRequestQueue::Steam_OnLeaderboardFindResult );
m_bQueryRunning = true;
break;
}
#endif
// Clean up all the requests in the queue
DevMsg( "CLeaderboardRequestQueue::OnStartNewQuery - request prepared.\n" );
// Run the query
OnSubmitQuery();
}
void CLeaderboardRequestQueue::OnSubmitQuery()
{
#ifdef _X360
if ( m_arrXuids.Count() && m_arrSpecs.Count() )
{
DWORD dwBytes = 0;
DWORD ret = XUserReadStats( 0,
m_arrXuids.Count(), m_arrXuids.Base(),
m_arrSpecs.Count(), m_arrSpecs.Base(),
&dwBytes, NULL, NULL );
if ( ret == ERROR_INSUFFICIENT_BUFFER )
{
ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
m_bufResults.EnsureCapacity( dwBytes );
ret = XUserReadStats( 0,
m_arrXuids.Count(), m_arrXuids.Base(),
m_arrSpecs.Count(), m_arrSpecs.Base(),
&dwBytes, ( PXUSER_STATS_READ_RESULTS ) m_bufResults.Base(),
&m_xOverlapped );
}
if ( ret == ERROR_IO_PENDING )
{
DevMsg( "CLeaderboardRequestQueue::OnSubmitQuery - query submitted...\n" );
m_bQueryRunning = true;
}
else
{
DevWarning( "CLeaderboardRequestQueue::OnSubmitQuery - failed [code = %d, bytes = %d]\n", ret, dwBytes );
}
}
#endif
}
#ifdef _X360
void CLeaderboardRequestQueue::ProcessResults( XUSER_STATS_READ_RESULTS const *pResults )
{
/*
901D41D61DC61 // XUID %llx
{
survival_c5m2_park
{
:rank = 923 // uint64
:rows = 999 // uint64
:rating = 600 // uint64
besttime = 600 // uint64
}
... more views ...
}
... more users ...
*/
DevMsg( "LeaderboardRequestQueue: ProcessResults ( %d views )\n", pResults->dwNumViews );
for ( DWORD k = 0; k < pResults->dwNumViews; ++ k )
{
XUSER_STATS_VIEW const *pView = &pResults->pViews[k];
Assert( pView );
KeyValues *pViewDesc = FindViewDescription( pView->dwViewId );
Assert( pViewDesc );
if ( !pViewDesc )
{
Warning( "LeaderboardRequestQueue: ProcessResults has no view description for view %d!\n", pView->dwViewId );
continue;
}
char const *szViewName = pViewDesc->GetString( ":name" );
DevMsg( " Processing view %d ( %s ), total rows = %d\n", pView->dwViewId, szViewName, pView->dwTotalViewRows );
for ( DWORD r = 0; r < pView->dwNumRows; ++ r )
{
XUSER_STATS_ROW const *pRow = &pView->pRows[r];
if ( !pRow->dwRank && !pRow->i64Rating )
{
DevMsg( " Gamer %s (%llx) not in view\n", pRow->szGamertag, pRow->xuid );
continue; // gamer is not present in the leaderboard
}
DevMsg( " Gamer %s (%llx) data loaded: rank=%d, rating=%lld\n",
pRow->szGamertag, pRow->xuid, pRow->dwRank, pRow->i64Rating );
// Gamer is present in the leaderboard and should be included in the results
if ( !m_pFinishedRequest )
m_pFinishedRequest = new KeyValues( "Leaderboard" );
// Find or create the view for this gamer
KeyValues *pUserInfo = m_pFinishedRequest->FindKey( CFmtStr( "%llx/%s", pRow->xuid, szViewName ), true );
// Set his rank and rating
pUserInfo->SetUint64( ":rank", pRow->dwRank );
pUserInfo->SetUint64( ":rows", pView->dwTotalViewRows );
pUserInfo->SetUint64( ":rating", pRow->i64Rating );
if ( char const *szName = pViewDesc->GetString( ":rating/name", NULL ) )
{
if ( szName[0] )
pUserInfo->SetUint64( szName, pRow->i64Rating );
}
// Process additional columns that were retrieved
Assert( !pRow->dwNumColumns );
// for ( DWORD c = 0; c < pRow->dwNumColumns; ++ c )
// {
// XUSER_STATS_COLUMN const *pCol = pRow->pColumns[ c ];
// KeyValues *pColDesc = FindViewColumnDesc( pViewDesc, pCol->wColumnId )
// }
}
}
DevMsg( "LeaderboardRequestQueue: ProcessResults finished.\n" );
}
#elif !defined( NO_STEAM )
void CLeaderboardRequestQueue::ProcessResults( LeaderboardEntry_t const &lbe )
{
/*
901D41D61DC61 // XUID %llx
{
survival_c5m2_park
{
:rank = 923 // uint64
:rows = 999 // uint64
:rating = 600 // uint64
besttime = 600 // uint64
}
... more views ...
}
... more users ...
*/
KeyValues *pViewDesc = m_pViewDescription;
Assert( pViewDesc );
if ( !pViewDesc )
{
Warning( "LeaderboardRequestQueue: ProcessResults has no view description for view!\n" );
return;
}
char const *szViewName = pViewDesc->GetString( ":name" );
DevMsg( " Processing view %s\n", szViewName );
DevMsg( " Gamer data loaded: rank=%d, score=%d\n",
lbe.m_nGlobalRank, lbe.m_nScore );
// Gamer is present in the leaderboard and should be included in the results
if ( !m_pFinishedRequest )
m_pFinishedRequest = new KeyValues( "Leaderboard" );
// Find or create the view for this gamer
KeyValues *pUserInfo = m_pFinishedRequest->FindKey( CFmtStr( "%llx/%s",
g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID(), szViewName ), true );
// Set user score
pUserInfo->SetUint64( pViewDesc->GetString( ":score" ), lbe.m_nScore );
DevMsg( "LeaderboardRequestQueue: ProcessResults finished.\n" );
}
#endif
#ifdef _X360
KeyValues * CLeaderboardRequestQueue::FindViewDescription( DWORD dwViewId )
{
if ( !dwViewId )
return NULL;
for ( int k = 0; k < m_arrViewDescriptions.Count(); ++ k )
{
KeyValues *pDesc = m_arrViewDescriptions[k];
if ( pDesc->GetInt( ":id" ) == ( int ) dwViewId )
return pDesc;
}
return NULL;
}
#elif !defined( NO_STEAM )
void CLeaderboardRequestQueue::Steam_OnLeaderboardFindResult( LeaderboardFindResult_t *p, bool bError )
{
if ( bError || !p->m_bLeaderboardFound )
{
DevMsg( "Steam leaderboard was not found.\n" );
OnQueryFinished();
return;
}
// Download the data
SteamAPICall_t hCall = steamapicontext->SteamUserStats()->DownloadLeaderboardEntries( p->m_hSteamLeaderboard,
k_ELeaderboardDataRequestGlobalAroundUser, 0, 0 );
m_CallbackOnLeaderboardScoresDownloaded.Set( hCall, this, &CLeaderboardRequestQueue::Steam_OnLeaderboardScoresDownloaded );
}
void CLeaderboardRequestQueue::Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError )
{
// Fetch the data if found and no error
LeaderboardEntry_t lbe;
if ( !bError &&
p->m_cEntryCount == 1 &&
steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( p->m_hSteamLeaderboardEntries, 0, &lbe, NULL, 0 ) )
{
ProcessResults( lbe );
}
OnQueryFinished();
}
#endif

View File

@@ -0,0 +1,21 @@
//========= Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef _LEADERBOARDS_H_
#define _LEADERBOARDS_H_
class ILeaderboardRequestQueue;
extern class ILeaderboardRequestQueue *g_pLeaderboardRequestQueue;
class ILeaderboardRequestQueue
{
public:
virtual void Request( KeyValues *pRequest ) = 0;
virtual void Update() = 0;
};
#endif // _LEADERBOARDS_H_

194
matchmaking/main.cpp Normal file
View File

@@ -0,0 +1,194 @@
//====== Copyright <20> 1996-2007, Valve Corporation, All rights reserved. =======//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#ifndef POSIX
#include <conio.h>
#include <direct.h>
#include <io.h>
#endif
#include "mm_framework.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// GCSDK uses the console log channel
DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_CONSOLE, "Console" );
void LinkMatchmakingLib()
{
// This function is required for the linker to include CMatchFramework
}
static CMatchFramework g_MatchFramework;
CMatchFramework *g_pMMF = &g_MatchFramework;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMatchFramework, IMatchFramework,
IMATCHFRAMEWORK_VERSION_STRING, g_MatchFramework );
//
// IAppSystem implementation
//
static CreateInterfaceFn s_pfnDelegateFactory;
static void * InternalFactory( const char *pName, int *pReturnCode )
{
if ( pReturnCode )
{
*pReturnCode = IFACE_OK;
}
// Try to get interface via delegate
if ( void *pInterface = s_pfnDelegateFactory ? s_pfnDelegateFactory( pName, pReturnCode ) : NULL )
{
return pInterface;
}
// Try to get internal interface
if ( void *pInterface = Sys_GetFactoryThis()( pName, pReturnCode ) )
{
return pInterface;
}
// Failed
if ( pReturnCode )
{
*pReturnCode = IFACE_FAILED;
}
return NULL;
}
namespace
{
typedef void * (CMatchExtensions::* ExtFn_t)();
struct MatchExtInterface_t
{
char const *m_szName;
ExtFn_t m_pfnGetInterface;
bool m_bConnected;
};
static MatchExtInterface_t s_table[] =
{
{ LOCALIZE_INTERFACE_VERSION, (ExtFn_t) &CMatchExtensions::GetILocalize, false },
{ INETSUPPORT_VERSION_STRING, (ExtFn_t) &CMatchExtensions::GetINetSupport, false },
{ IENGINEVOICE_INTERFACE_VERSION, (ExtFn_t) &CMatchExtensions::GetIEngineVoice, false },
{ VENGINE_CLIENT_INTERFACE_VERSION, (ExtFn_t) &CMatchExtensions::GetIVEngineClient, false },
{ INTERFACEVERSION_VENGINESERVER, (ExtFn_t) &CMatchExtensions::GetIVEngineServer, false },
{ INTERFACEVERSION_GAMEEVENTSMANAGER2, (ExtFn_t) &CMatchExtensions::GetIGameEventManager2, false },
#ifdef _X360
{ XBOXSYSTEM_INTERFACE_VERSION, (ExtFn_t) &CMatchExtensions::GetIXboxSystem, false },
{ XONLINE_INTERFACE_VERSION, (ExtFn_t) &CMatchExtensions::GetIXOnline, false },
#endif
{ NULL, NULL, NULL }
};
};
bool CMatchFramework::Connect( CreateInterfaceFn factory )
{
Assert( !s_pfnDelegateFactory );
s_pfnDelegateFactory = factory;
CreateInterfaceFn ourFactory = InternalFactory;
ConnectTier1Libraries( &ourFactory, 1 );
ConnectTier2Libraries( &ourFactory, 1 );
ConVar_Register();
// Get our extension interfaces
for ( MatchExtInterface_t *ptr = s_table; ptr->m_szName; ++ ptr )
{
if ( !ptr->m_bConnected
// && !(g_pMatchExtensions->*(ptr->m_pfnGetInterface))()
)
{
void *pvInterface = ourFactory( ptr->m_szName, NULL );
if ( pvInterface )
{
g_pMatchExtensions->RegisterExtensionInterface( ptr->m_szName, pvInterface );
ptr->m_bConnected = true;
}
}
}
s_pfnDelegateFactory = NULL;
MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f );
SteamApiContext_Init();
#if !defined( _GAMECONSOLE ) && !defined( SWDS )
// Trigger intialization from Steam users
if ( g_pPlayerManager )
g_pPlayerManager->OnGameUsersChanged();
#endif
return true;
}
void CMatchFramework::Disconnect()
{
SteamApiContext_Shutdown();
for ( MatchExtInterface_t *ptr = s_table; ptr->m_szName; ++ ptr )
{
if ( ptr->m_bConnected )
{
void *pvInterface = (g_pMatchExtensions->*(ptr->m_pfnGetInterface))();
Assert( pvInterface );
g_pMatchExtensions->UnregisterExtensionInterface( ptr->m_szName, pvInterface );
ptr->m_bConnected = false;
}
}
DisconnectTier2Libraries();
ConVar_Unregister();
DisconnectTier1Libraries();
}
void * CMatchFramework::QueryInterface( const char *pInterfaceName )
{
if ( !Q_stricmp( pInterfaceName, IMATCHFRAMEWORK_VERSION_STRING ) )
return static_cast< IMatchFramework* >( this );
return NULL;
}
const char *COM_GetModDirectory()
{
static char modDir[MAX_PATH];
if ( Q_strlen( modDir ) == 0 )
{
const char *gamedir = CommandLine()->ParmValue("-game", CommandLine()->ParmValue( "-defaultgamedir", "hl2" ) );
Q_strncpy( modDir, gamedir, sizeof(modDir) );
if ( strchr( modDir, '/' ) || strchr( modDir, '\\' ) )
{
Q_StripLastDir( modDir, sizeof(modDir) );
int dirlen = Q_strlen( modDir );
Q_strncpy( modDir, gamedir + dirlen, sizeof(modDir) - dirlen );
}
}
return modDir;
}
bool IsLocalClientConnectedToServer()
{
return
( g_pMatchExtensions &&
g_pMatchExtensions->GetIVEngineClient() &&
( g_pMatchExtensions->GetIVEngineClient()->IsConnected() ||
g_pMatchExtensions->GetIVEngineClient()->IsDrawingLoadingImage() ||
g_pMatchExtensions->GetIVEngineClient()->IsTransitioningToLoad() ) );
}

View File

@@ -0,0 +1,794 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_framework.h"
#include "vstdlib/random.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static ConVar mm_session_search_num_results( "mm_session_search_num_results", "10", FCVAR_DEVELOPMENTONLY );
static ConVar mm_session_search_distance( "mm_session_search_distance", "1", FCVAR_DEVELOPMENTONLY );
ConVar mm_session_search_qos_timeout( "mm_session_search_qos_timeout", "15.0", FCVAR_RELEASE );
// static ConVar mm_session_search_ping_limit( "mm_session_search_ping_limit", "200", FCVAR_RELEASE ); -- removed, using mm_dedicated_search_maxping instead
static ConVar mm_session_search_ping_buckets( "mm_session_search_ping_buckets", "4", FCVAR_RELEASE );
CMatchSearcher::CMatchSearcher( KeyValues *pSettings ) :
m_pSettings( pSettings ), // takes ownership
m_autodelete_pSettings( m_pSettings ),
m_pSessionSearchTree( NULL ),
m_autodelete_pSessionSearchTree( m_pSessionSearchTree ),
m_pSearchPass( NULL ),
m_eState( STATE_INIT )
{
#ifdef _X360
ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
m_pQosResults = NULL;
m_pCancelOverlappedJob = NULL;
#endif
DevMsg( "Created CMatchSearcher:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
InitializeSettings();
}
CMatchSearcher::~CMatchSearcher()
{
DevMsg( "Destroying CMatchSearcher:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
// Free all retrieved game details
CUtlVector< SearchResult_t > *arrResults[] = { &m_arrSearchResults, &m_arrSearchResultsAggregate };
for ( int ia = 0; ia < ARRAYSIZE( arrResults ); ++ ia )
{
for ( int k = 0; k < arrResults[ia]->Count(); ++ k )
{
SearchResult_t &sr = arrResults[ia]->Element( k );
if ( sr.m_pGameDetails )
{
sr.m_pGameDetails->deleteThis();
sr.m_pGameDetails = NULL;
}
}
}
}
void CMatchSearcher::InitializeSettings()
{
// Initialize only the settings required to connect...
if ( KeyValues *kv = m_pSettings->FindKey( "system", true ) )
{
KeyValuesAddDefaultString( kv, "network", "LIVE" );
KeyValuesAddDefaultString( kv, "access", "public" );
}
if ( KeyValues *pMembers = m_pSettings->FindKey( "members", true ) )
{
int numMachines = pMembers->GetInt( "numMachines", -1 );
if ( numMachines == -1 )
{
numMachines = 1;
pMembers->SetInt( "numMachines", numMachines );
}
int numPlayers = pMembers->GetInt( "numPlayers", -1 );
if ( numPlayers == -1 )
{
numPlayers = 1;
#ifdef _GAMECONSOLE
numPlayers = XBX_GetNumGameUsers();
#endif
pMembers->SetInt( "numPlayers", numPlayers );
}
pMembers->SetInt( "numSlots", numPlayers );
KeyValues *pMachine = pMembers->FindKey( "machine0");
if ( !pMachine )
{
pMachine = pMembers->CreateNewKey();
pMachine->SetName( "machine0" );
XUID machineid = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
pMachine->SetUint64( "id", machineid );
pMachine->SetUint64( "flags", MatchSession_GetMachineFlags() );
pMachine->SetInt( "numPlayers", numPlayers );
pMachine->SetUint64( "dlcmask", g_pMatchFramework->GetMatchSystem()->GetDlcManager()->GetDataInfo()->GetUint64( "@info/installed" ) );
pMachine->SetString( "tuver", MatchSession_GetTuInstalledString() );
pMachine->SetInt( "ping", 0 );
for ( int k = 0; k < numPlayers; ++ k )
{
if ( KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", k ), true ) )
{
int iController = 0;
#ifdef _GAMECONSOLE
iController = XBX_GetUserId( k );
#endif
IPlayerLocal *player = g_pPlayerManager->GetLocalPlayer( iController );
pPlayer->SetUint64( "xuid", player->GetXUID() );
pPlayer->SetString( "name", player->GetName() );
}
}
}
}
DevMsg( "CMatchSearcher::InitializeGameSettings adjusted settings:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
KeyValues * CMatchSearcher::GetSearchSettings()
{
return m_pSettings;
}
void CMatchSearcher::Destroy()
{
// Stop the search
if ( m_eState == STATE_SEARCHING )
{
#ifdef _X360
m_pCancelOverlappedJob = ThreadExecute( MMX360_CancelOverlapped, &m_xOverlapped ); // UpdateDormantOperations will clean the rest
MMX360_RegisterDormant( this );
return;
#endif
}
#ifdef _X360
if ( m_eState == STATE_CHECK_QOS )
{
g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pQosResults );
m_pQosResults = NULL;
}
#endif
#if !defined( NO_STEAM )
while ( m_arrOutstandingAsyncOperation.Count() > 0 )
{
IMatchAsyncOperation *pAsyncOperation = m_arrOutstandingAsyncOperation.Tail();
m_arrOutstandingAsyncOperation.RemoveMultipleFromTail( 1 );
if ( pAsyncOperation )
pAsyncOperation->Release();
}
#endif
delete this;
}
void CMatchSearcher::OnSearchEvent( KeyValues *pNotify )
{
g_pMatchEventsSubscription->BroadcastEvent( pNotify );
}
#ifdef _X360
bool CMatchSearcher::UpdateDormantOperation()
{
if ( !m_pCancelOverlappedJob->IsFinished() )
return true; // keep running dormant
m_pCancelOverlappedJob->Release();
m_pCancelOverlappedJob = NULL;
delete this;
return false; // destroyed object, remove from dormant list
}
#endif
void CMatchSearcher::Update()
{
switch ( m_eState )
{
case STATE_INIT:
m_eState = STATE_SEARCHING;
// Session is searching
OnSearchEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "searching"
) );
// Kick off the search
StartSearch();
break;
case STATE_SEARCHING:
// Waiting for session search to complete
#ifdef _X360
if ( XHasOverlappedIoCompleted( &m_xOverlapped ) )
Live_OnSessionSearchCompleted();
#endif
break;
#ifdef _X360
case STATE_CHECK_QOS:
// Keep checking for results or until the wait time expires
if ( Plat_FloatTime() > m_flQosTimeout ||
!m_pQosResults->cxnqosPending )
Live_OnQosCheckCompleted();
break;
#endif
#if !defined (NO_STEAM)
case STATE_WAITING_LOBBY_DATA_AND_PING:
{
bool bWaitLonger = ( ( Plat_MSTime() - m_uiQosTimeoutStartMS ) < ( mm_session_search_qos_timeout.GetFloat() * 1000.f ) );
if ( bWaitLonger )
{
bool bHasPendingLobbyData = false;
for ( int j = 0; j < m_arrSearchResults.Count(); ++ j )
{
SearchResult_t &sr = m_arrSearchResults[j];
if ( !sr.m_numPlayers )
{ // didn't even receive lobby data from Steam yet
bHasPendingLobbyData = true;
}
else if ( sr.m_svAdr.GetIPHostByteOrder() && ( sr.m_svPing <= 0 ) )
{ // received valid IP for lobby, still pinging
bHasPendingLobbyData = true;
}
}
if ( !bHasPendingLobbyData )
bWaitLonger = false;
}
if ( !bWaitLonger )
{
m_CallbackOnLobbyDataReceived.Unregister();
// Go ahead and start joining the results
AggregateSearchPassResults();
OnSearchPassDone( m_pSearchPass );
}
}
break;
#endif
}
}
#if !defined (NO_STEAM)
void CMatchSearcher::Steam_OnLobbyDataReceived( LobbyDataUpdate_t *pLobbyDataUpdate )
{
int iResultIndex = 0;
for ( ; iResultIndex < m_arrSearchResults.Count(); ++ iResultIndex )
{
if ( m_arrSearchResults[ iResultIndex ].m_uiLobbyId == pLobbyDataUpdate->m_ulSteamIDLobby )
break;
}
if ( !m_arrSearchResults.IsValidIndex( iResultIndex ) )
return;
SearchResult_t *pRes = &m_arrSearchResults[ iResultIndex ];
if ( !pLobbyDataUpdate->m_bSuccess )
{
DevMsg( "[MM] Could not get lobby data for lobby %llu (%llx)\n",
pRes->m_uiLobbyId, pRes->m_uiLobbyId );
pRes->m_numPlayers = -1; // set numPlayers to negative number to indicate a failure here
}
else
{
// Get num players
const char *numPlayers = steamapicontext->SteamMatchmaking()->GetLobbyData(
pRes->m_uiLobbyId, "members:numPlayers" );
if ( !numPlayers )
{
DevMsg( "[MM] Unable to get num players for lobby (%llx)\n",
pRes->m_uiLobbyId );
pRes->m_numPlayers = -1; // set numPlayers to negative number to indicate a failure here
}
else
{
pRes->m_numPlayers = V_atoi( numPlayers );
if ( !pRes->m_numPlayers )
pRes->m_numPlayers = -1; // set numPlayers to negative number to indicate a failure here
}
// Get the address of the server
const char* pServerAdr = steamapicontext->SteamMatchmaking()->GetLobbyData(
pRes->m_uiLobbyId, "server:adronline" );
if ( !pServerAdr )
{
// DevMsg( "[MM] Unable to get server address from lobby (%llx)\n",
// pRes->m_uiLobbyId );
}
else
{
pRes->m_svAdr.SetFromString( pServerAdr );
DevMsg ( "[MM] Lobby %d: id %llu (%llx), num Players %d, sv ip %s, pinging dist %d\n",
iResultIndex, pRes->m_uiLobbyId, pRes->m_uiLobbyId, pRes->m_numPlayers,
pRes->m_svAdr.ToString(), pRes->m_svPing );
// Ping server
IMatchAsyncOperation *pAsyncOperationPing = NULL;
g_pMatchExtensions->GetINetSupport()->ServerPing( pRes->m_svAdr, this, &pAsyncOperationPing );
m_arrOutstandingAsyncOperation.AddToTail( pAsyncOperationPing );
pRes->m_pAsyncOperationPingWeakRef = pAsyncOperationPing;
}
}
}
// Callback for server reservation check
void CMatchSearcher::OnOperationFinished( IMatchAsyncOperation *pOperation )
{
if ( !pOperation )
return;
if ( m_eState != STATE_WAITING_LOBBY_DATA_AND_PING )
return;
for ( int i = 0; i < m_arrSearchResults.Count(); ++ i )
{
if ( m_arrSearchResults[i].m_pAsyncOperationPingWeakRef != pOperation )
continue;
int result = pOperation->GetResult();
bool failed = ( pOperation->GetState() == AOS_FAILED );
SearchResult_t *pRes = &m_arrSearchResults[i];
pRes->m_svPing = ( failed ? -1 : ( result ? result : -1 ) );
if ( pRes->m_svPing < 0 )
{
DevMsg( "[MM] Failed pinging server %s for lobby#%d %llu (%llx)\n",
pRes->m_svAdr.ToString(), i, pRes->m_uiLobbyId, pRes->m_uiLobbyId );
}
else
{
DevMsg( "[MM] Successfully pinged server %s for lobby#%d %llu (%llx) = %d ms\n",
pRes->m_svAdr.ToString(), i, pRes->m_uiLobbyId, pRes->m_uiLobbyId, pRes->m_svPing );
}
m_arrSearchResults[i].m_pAsyncOperationPingWeakRef = NULL;
}
}
#endif
void CMatchSearcher::OnSearchDone()
{
m_eState = STATE_DONE;
}
void CMatchSearcher::AggregateSearchPassResults()
{
#ifndef NO_STEAM
extern ConVar mm_dedicated_search_maxping;
int nPingLimitMax = mm_dedicated_search_maxping.GetInt();
if ( nPingLimitMax <= 0 )
nPingLimitMax = 5000;
for ( int k = 0; k < mm_session_search_ping_buckets.GetInt(); ++ k )
{
int iPingLow = ( nPingLimitMax * k ) / mm_session_search_ping_buckets.GetInt();
int iPingHigh = ( nPingLimitMax * ( k + 1 ) ) / mm_session_search_ping_buckets.GetInt();
for ( int iResult = 0; iResult < m_arrSearchResults.Count(); ++ iResult )
{
SearchResult_t *pRes = &m_arrSearchResults[iResult];
if ( ( pRes->m_svPing > iPingLow ) && ( pRes->m_svPing <= iPingHigh ) )
{
m_arrSearchResultsAggregate.AddToTail( *pRes );
DevMsg ( "[MM] Search aggregated result%d / %d: id %llu (%llx), num Players %d, sv ip %s, dist %d\n",
m_arrSearchResultsAggregate.Count(), iResult, pRes->m_uiLobbyId, pRes->m_uiLobbyId, pRes->m_numPlayers,
pRes->m_svAdr.ToString(), pRes->m_svPing );
}
}
}
DevMsg ( "[MM] Search aggregated %d results\n", m_arrSearchResultsAggregate.Count() );
#endif
m_arrSearchResults.Purge();
}
void CMatchSearcher::OnSearchPassDone( KeyValues *pSearchPass )
{
// If we have enough results, then call it done
if ( m_arrSearchResultsAggregate.Count() >= mm_session_search_num_results.GetInt() )
{
OnSearchDone();
return;
}
// Evaluate if there is a nextpass condition
char const *szNextPassKeyName = "nextpass";
if ( KeyValues *pConditions = pSearchPass->FindKey( "nextpass?" ) )
{
// Inspect conditions and select which next pass will happen
}
if ( KeyValues *pNextPass = pSearchPass->FindKey( szNextPassKeyName ) )
{
StartSearchPass( pNextPass );
}
else
{
OnSearchDone();
}
}
#ifdef _X360
void CMatchSearcher::Live_OnSessionSearchCompleted()
{
DevMsg( "Received %d search results from Xbox LIVE.\n", GetXSearchResult()->dwSearchResults );
for( unsigned int i = 0; i < GetXSearchResult()->dwSearchResults; ++ i )
{
XSESSION_SEARCHRESULT const &xsr = GetXSearchResult()->pResults[i];
SearchResult_t sr = { xsr.info, NULL };
m_arrSearchResults.AddToTail( sr );
DevMsg( 2, "Result #%02d: %llx\n", i + 1, ( const uint64& ) xsr.info.sessionID );
}
if ( !m_arrSearchResults.Count() )
{
OnSearchPassDone( m_pSearchPass );
}
else
{
DevMsg( "Checking QOS with %d search results.\n", m_arrSearchResults.Count() );
Live_CheckSearchResultsQos();
}
}
void CMatchSearcher::Live_CheckSearchResultsQos()
{
m_eState = STATE_CHECK_QOS;
int nResults = m_arrSearchResults.Count();
CUtlVector< const void * > memQosData;
memQosData.SetCount( 3 * nResults );
const void ** bufQosData[3];
for ( int k = 0; k < ARRAYSIZE( bufQosData ); ++ k )
bufQosData[k] = &memQosData[ k * nResults ];
for ( int k = 0; k < m_arrSearchResults.Count(); ++ k )
{
SearchResult_t const &sr = m_arrSearchResults[k];
bufQosData[0][k] = &sr.m_info.hostAddress;
bufQosData[1][k] = &sr.m_info.sessionID;
bufQosData[2][k] = &sr.m_info.keyExchangeKey;
}
//
// Note: XNetQosLookup requires only 2 successful probes to be received from the host.
// This is much less than the recommended 8 probes because on a 10% data loss profile
// it is impossible to find the host when requiring 8 probes to be received.
m_flQosTimeout = Plat_FloatTime() + mm_session_search_qos_timeout.GetFloat();
int res = g_pMatchExtensions->GetIXOnline()->XNetQosLookup(
nResults,
reinterpret_cast< XNADDR const ** >( bufQosData[0] ),
reinterpret_cast< XNKID const ** >( bufQosData[1] ),
reinterpret_cast< XNKEY const ** >( bufQosData[2] ),
0, // number of security gateways to probe
NULL, // gateway ip addresses
NULL, // gateway service ids
2, // number of probes
0, // upstream bandwith to use (0 = default)
0, // flags - not supported
NULL, // signal event
&m_pQosResults );// results
if ( res != 0 )
{
DevWarning( "OnlineSearch::Live_CheckSearchResultsQos - XNetQosLookup failed (code = 0x%08X)!\n", res );
m_arrSearchResults.Purge();
OnSearchPassDone( m_pSearchPass );
}
}
void CMatchSearcher::Live_OnQosCheckCompleted()
{
for ( uint k = m_pQosResults->cxnqos; k --> 0; )
{
XNQOSINFO &xqi = m_pQosResults->axnqosinfo[k];
BYTE uNeedFlags = XNET_XNQOSINFO_TARGET_CONTACTED | XNET_XNQOSINFO_DATA_RECEIVED;
if ( ( ( xqi.bFlags & uNeedFlags ) != uNeedFlags) ||
( xqi.bFlags & XNET_XNQOSINFO_TARGET_DISABLED ) )
{
m_arrSearchResults.Remove( k );
continue;
}
extern ConVar mm_dedicated_search_maxping;
if ( mm_dedicated_search_maxping.GetInt() > 0 &&
xqi.wRttMedInMsecs > mm_dedicated_search_maxping.GetInt() )
{
m_arrSearchResults.Remove( k );
continue;
}
if ( xqi.cbData && xqi.pbData )
{
MM_GameDetails_QOS_t gd = { xqi.pbData, xqi.cbData, xqi.wRttMedInMsecs };
Assert( !m_arrSearchResults[k].m_pGameDetails );
m_arrSearchResults[k].m_pGameDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromQOS( &gd );
}
}
g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pQosResults );
m_pQosResults = NULL;
// Go ahead and start joining the results
DevMsg( "Qos completed with %d search results.\n", m_arrSearchResults.Count() );
AggregateSearchPassResults();
OnSearchPassDone( m_pSearchPass );
}
#elif !defined( NO_STEAM )
void CMatchSearcher::Steam_OnLobbyMatchListReceived( LobbyMatchList_t *pLobbyMatchList, bool bError )
{
Msg( "[MM] Received %d search results.\n", bError ? 0 : pLobbyMatchList->m_nLobbiesMatching );
m_CallbackOnLobbyDataReceived.Register( this, &CMatchSearcher::Steam_OnLobbyDataReceived );
// Walk through search results and request lobby data
for ( int iLobby = 0; iLobby < (int) ( bError ? 0 : pLobbyMatchList->m_nLobbiesMatching ); ++ iLobby )
{
uint64 uiLobbyId = steamapicontext->SteamMatchmaking()->GetLobbyByIndex( iLobby ).ConvertToUint64();
SearchResult_t sr = { uiLobbyId };
sr.m_pGameDetails = NULL;
sr.m_svAdr.SetFromString( "0.0.0.0" );
sr.m_svPing = 0;
sr.m_numPlayers = 0;
sr.m_pAsyncOperationPingWeakRef = NULL;
m_arrSearchResults.AddToTail( sr );
steamapicontext->SteamMatchmaking()->RequestLobbyData( sr.m_uiLobbyId );
}
m_eState = STATE_WAITING_LOBBY_DATA_AND_PING; // Waiting for lobby data
m_uiQosPingLastMS = m_uiQosTimeoutStartMS = Plat_MSTime();
}
KeyValues * CMatchSearcher::SearchResult_t::GetGameDetails() const
{
if ( !m_pGameDetails )
{
m_pGameDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromSteamLobby( m_uiLobbyId );
}
return m_pGameDetails;
}
#endif
void CMatchSearcher::StartSearch()
{
Assert( !m_pSessionSearchTree );
m_pSessionSearchTree = g_pMMF->GetMatchTitleGameSettingsMgr()->DefineSessionSearchKeys( m_pSettings );
m_autodelete_pSessionSearchTree.Assign( m_pSessionSearchTree );
Assert( m_pSessionSearchTree );
if ( !m_pSessionSearchTree )
{
DevWarning( "OnlineSearch::StartSearch failed to build filter list!\n" );
OnSearchDone();
}
else
{
StartSearchPass( m_pSessionSearchTree );
}
}
void CMatchSearcher::StartSearchPass( KeyValues *pSearchPass )
{
KeyValues *pSearchParams = pSearchPass;
m_pSearchPass = pSearchPass;
// Make sure we have fresh buffer for search results
m_arrSearchResults.Purge();
m_eState = STATE_SEARCHING;
DevMsg( "OnlineSearch::StartSearchPass:\n" );
KeyValuesDumpAsDevMsg( pSearchParams, 1 );
#ifdef _X360
DWORD dwSearchRule = pSearchParams->GetInt( "rule" );
m_arrContexts.RemoveAll();
if ( KeyValues *pContexts = pSearchParams->FindKey( "Contexts" ) )
{
for ( KeyValues *val = pContexts->GetFirstValue(); val; val = val->GetNextValue() )
{
XUSER_CONTEXT ctx = { 0 };
ctx.dwContextId = atoi( val->GetName() );
if ( val->GetDataType() == KeyValues::TYPE_INT )
{
ctx.dwValue = val->GetInt();
m_arrContexts.AddToTail( ctx );
}
}
}
m_arrProperties.RemoveAll();
if ( KeyValues *pContexts = pSearchParams->FindKey( "Properties" ) )
{
for ( KeyValues *val = pContexts->GetFirstValue(); val; val = val->GetNextValue() )
{
XUSER_PROPERTY prop = { 0 };
prop.dwPropertyId = atoi( val->GetName() );
if ( val->GetDataType() == KeyValues::TYPE_INT )
{
prop.value.type = XUSER_DATA_TYPE_INT32;
prop.value.nData = val->GetInt();
m_arrProperties.AddToTail( prop );
}
}
}
DWORD ret = ERROR_SUCCESS;
DWORD numBytes = 0;
DWORD dwNumSlotsRequired = pSearchParams->GetInt( "numPlayers" );
//
// Issue the asynchrounous session search request
//
ret = g_pMatchExtensions->GetIXOnline()->XSessionSearchEx(
dwSearchRule, XBX_GetPrimaryUserId(), mm_session_search_num_results.GetInt(),
dwNumSlotsRequired,
m_arrProperties.Count(), m_arrContexts.Count(),
m_arrProperties.Base(), m_arrContexts.Base(),
&numBytes, NULL, NULL
);
// Log the search request to read X360 queries easier
DevMsg( "XSessionSearchEx by rule %d for slots %d\n", dwSearchRule, dwNumSlotsRequired );
for ( int k = 0; k < m_arrContexts.Count(); ++ k )
DevMsg( " CTX %u/0x%08X = 0x%X/%u\n", m_arrContexts[k].dwContextId, m_arrContexts[k].dwContextId, m_arrContexts[k].dwValue, m_arrContexts[k].dwValue );
for ( int k = 0; k < m_arrProperties.Count(); ++ k )
DevMsg( " PRP %u/0x%08X = 0x%X/%u\n", m_arrProperties[k].dwPropertyId, m_arrProperties[k].dwPropertyId, m_arrProperties[k].value.nData, m_arrProperties[k].value.nData );
DevMsg( "will use %u bytes buffer.\n", numBytes );
if ( ERROR_INSUFFICIENT_BUFFER == ret && numBytes > 0 )
{
m_bufSearchResultHeader.EnsureCapacity( numBytes );
ZeroMemory( GetXSearchResult(), numBytes );
ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
DevMsg( "Searching...\n" );
ret = g_pMatchExtensions->GetIXOnline()->XSessionSearchEx(
dwSearchRule, XBX_GetPrimaryUserId(), mm_session_search_num_results.GetInt(),
dwNumSlotsRequired,
m_arrProperties.Count(), m_arrContexts.Count(),
m_arrProperties.Base(), m_arrContexts.Base(),
&numBytes, GetXSearchResult(), &m_xOverlapped
);
if ( ret == ERROR_IO_PENDING )
return;
}
// Otherwise search failed
DevWarning( "XSessionSearchEx failed (code = 0x%08X)\n", ret );
ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
OnSearchPassDone( m_pSearchPass );
#elif !defined( NO_STEAM )
ISteamMatchmaking *mm = steamapicontext->SteamMatchmaking();
// Configure filters
DWORD dwNumSlotsRequired = pSearchParams->GetInt( "numPlayers" );
mm->AddRequestLobbyListFilterSlotsAvailable( dwNumSlotsRequired );
// Set filters
char const * arrKeys[] = { "Filter<", "Filter<=", "Filter=", "Filter<>", "Filter>=", "Filter>" };
ELobbyComparison nValueCmp[] = { k_ELobbyComparisonLessThan, k_ELobbyComparisonEqualToOrLessThan, k_ELobbyComparisonEqual, k_ELobbyComparisonNotEqual, k_ELobbyComparisonEqualToOrGreaterThan, k_ELobbyComparisonGreaterThan };
for ( int k = 0; k < ARRAYSIZE( arrKeys ); ++ k )
{
if ( KeyValues *kv = pSearchParams->FindKey( arrKeys[k] ) )
{
for ( KeyValues *val = kv->GetFirstValue(); val; val = val->GetNextValue() )
{
if ( val->GetDataType() == KeyValues::TYPE_STRING )
{
mm->AddRequestLobbyListStringFilter( val->GetName(), val->GetString(), nValueCmp[k] );
}
else if ( val->GetDataType() == KeyValues::TYPE_INT )
{
mm->AddRequestLobbyListNumericalFilter( val->GetName(), val->GetInt(), nValueCmp[k] );
}
}
}
}
// Set ordering near values
if ( KeyValues *kv = pSearchParams->FindKey( "Near" ) )
{
for ( KeyValues *val = kv->GetFirstValue(); val; val = val->GetNextValue() )
{
if ( val->GetDataType() == KeyValues::TYPE_INT )
{
mm->AddRequestLobbyListNearValueFilter( val->GetName(), val->GetInt() );
}
else
{
// TODO: need to set near value filter for strings
}
}
}
ELobbyDistanceFilter eFilter = k_ELobbyDistanceFilterDefault;
switch ( mm_session_search_distance.GetInt() )
{
case k_ELobbyDistanceFilterClose:
case k_ELobbyDistanceFilterDefault:
case k_ELobbyDistanceFilterFar:
case k_ELobbyDistanceFilterWorldwide:
eFilter = ( ELobbyDistanceFilter ) mm_session_search_distance.GetInt();
break;
}
// Set distance filter to Near
mm->AddRequestLobbyListDistanceFilter( eFilter );
// Set dependent lobby
if ( uint64 uiDependentLobbyId = pSearchParams->GetUint64( "DependentLobby", 0ull ) )
{
mm->AddRequestLobbyListCompatibleMembersFilter( uiDependentLobbyId );
}
// RequestLobbyList - will get called back at Steam_OnLobbyMatchListReceived
DevMsg( "Searching...\n" );
SteamAPICall_t hCall = mm->RequestLobbyList();
m_CallbackOnLobbyMatchListReceived.Set( hCall, this, &CMatchSearcher::Steam_OnLobbyMatchListReceived );
#endif
}
//
// Results retrieval overrides
//
bool CMatchSearcher::IsSearchFinished() const
{
return m_eState == STATE_DONE;
}
int CMatchSearcher::GetNumSearchResults() const
{
return IsSearchFinished() ? m_arrSearchResultsAggregate.Count() : 0;
}
CMatchSearcher::SearchResult_t const & CMatchSearcher::GetSearchResult( int idx ) const
{
if ( !IsSearchFinished() || !m_arrSearchResultsAggregate.IsValidIndex( idx ) )
{
Assert( false );
static SearchResult_t s_empty;
return s_empty;
}
else
{
return m_arrSearchResultsAggregate[ idx ];
}
}

View File

@@ -0,0 +1,179 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MATCH_SEARCHER_H
#define MATCH_SEARCHER_H
#ifdef _WIN32
#pragma once
#endif
#if !defined( NO_STEAM )
#include "steam/steam_api.h"
#endif // _X360
//
// CMatchSearcher
//
class CMatchSearcher
#ifdef _X360
: public IDormantOperation
#endif
#if !defined (NO_STEAM)
: public IMatchAsyncOperationCallback
#endif
{
public:
explicit CMatchSearcher( KeyValues *pSettings );
virtual ~CMatchSearcher();
public:
// Run a frame update
virtual void Update();
// Destroy the object
virtual void Destroy();
//
// Overrides
//
public:
// Obtain adjusted match search settings
virtual KeyValues * GetSearchSettings();
// Event broadcasting
virtual void OnSearchEvent( KeyValues *pNotify );
protected:
#ifdef _X360
public:
struct SearchResult_t
{
inline XNKID GetXNKID() const { return m_info.sessionID; }
KeyValues * GetGameDetails() const { return m_pGameDetails; }
XSESSION_INFO m_info;
KeyValues *m_pGameDetails;
};
protected:
CUtlVector< XUSER_CONTEXT > m_arrContexts;
CUtlVector< XUSER_PROPERTY > m_arrProperties;
float m_flQosTimeout;
XNQOS *m_pQosResults;
CUtlBuffer m_bufSearchResultHeader;
XSESSION_SEARCHRESULT_HEADER * GetXSearchResult() { return ( XSESSION_SEARCHRESULT_HEADER * ) m_bufSearchResultHeader.Base(); }
XOVERLAPPED m_xOverlapped;
CJob *m_pCancelOverlappedJob;
void Live_OnSessionSearchCompleted();
void Live_CheckSearchResultsQos();
void Live_OnQosCheckCompleted();
virtual bool UpdateDormantOperation();
#elif !defined( NO_STEAM )
public:
struct SearchResult_t
{
inline XNKID GetXNKID() const { return ( const XNKID & ) m_uiLobbyId; }
KeyValues * GetGameDetails() const;
uint64 m_uiLobbyId;
mutable KeyValues *m_pGameDetails;
netadr_t m_svAdr;
int m_svPing;
int m_numPlayers;
IMatchAsyncOperation *m_pAsyncOperationPingWeakRef;
};
protected:
CCallResult< CMatchSearcher, LobbyMatchList_t > m_CallbackOnLobbyMatchListReceived;
void Steam_OnLobbyMatchListReceived( LobbyMatchList_t *p, bool bError );
#else
public:
struct SearchResult_t
{
inline XNKID GetXNKID() const { return ( const XNKID & ) m_uiLobbyId; }
KeyValues * GetGameDetails() const { return m_pGameDetails; }
uint64 m_uiLobbyId;
KeyValues *m_pGameDetails;
};
#endif
protected:
KeyValues *m_pSettings;
KeyValues::AutoDelete m_autodelete_pSettings;
KeyValues *m_pSessionSearchTree;
KeyValues::AutoDelete m_autodelete_pSessionSearchTree;
KeyValues *m_pSearchPass;
#if !defined( NO_STEAM )
uint32 m_uiQosTimeoutStartMS;
uint32 m_uiQosPingLastMS;
CUtlVector< IMatchAsyncOperation * > m_arrOutstandingAsyncOperation;
STEAM_CALLBACK_MANUAL( CMatchSearcher, Steam_OnLobbyDataReceived, LobbyDataUpdate_t, m_CallbackOnLobbyDataReceived );
// Callback for server reservation check
virtual void OnOperationFinished( IMatchAsyncOperation *pOperation );
#endif
enum State_t
{
STATE_INIT,
STATE_SEARCHING,
#ifdef _X360
STATE_CHECK_QOS,
#endif
#if !defined( NO_STEAM )
STATE_WAITING_LOBBY_DATA_AND_PING,
#endif
STATE_DONE
};
State_t m_eState;
CUtlVector< SearchResult_t > m_arrSearchResults;
CUtlVector< SearchResult_t > m_arrSearchResultsAggregate;
protected:
void InitializeSettings();
void StartSearch();
virtual void StartSearchPass( KeyValues *pSearchPass );
void AggregateSearchPassResults();
virtual void OnSearchPassDone( KeyValues *pSearchPass );
virtual void OnSearchDone();
//
// Results retrieval overrides
//
public:
virtual bool IsSearchFinished() const;
virtual int GetNumSearchResults() const;
virtual SearchResult_t const & GetSearchResult( int idx ) const;
};
#endif // MATCH_SEARCHER_H

View File

@@ -0,0 +1,42 @@
//-----------------------------------------------------------------------------
// matchmaking_base.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTLIBNAME "matchmakingbase"
$include "matchmaking_base_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DLL"
}
}
$Configuration "Debug"
{
$General
{
$IntermediateDirectory ".\Debug_matchmakingbase$PLATSUBDIR" [$WINDOWS||$POSIX]
$IntermediateDirectory ".\Debug_matchmakingbase_360" [$X360]
$IntermediateDirectory ".\Debug_matchmakingbase_PS3" [$PS3]
}
}
$Configuration "Release"
{
$General
{
$IntermediateDirectory ".\Release_matchmakingbase$PLATSUBDIR" [$WINDOWS||$POSIX]
$IntermediateDirectory ".\Release_matchmakingbase_360" [$X360]
$IntermediateDirectory ".\Release_matchmakingbase_PS3" [$PS3]
}
}
$Project "matchmakingbase"
{
}

View File

@@ -0,0 +1,13 @@
"vpc_cache"
{
"CacheVersion" "1"
"win32"
{
"CRCFile" "matchmakingbase.vcxproj.vpc_crc"
"OutputFiles"
{
"0" "matchmakingbase.vcxproj"
"1" "matchmakingbase.vcxproj.filters"
}
}
}

View File

@@ -0,0 +1,98 @@
//-----------------------------------------------------------------------------
// matchmaking_base_ds.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTLIBNAME "matchmakingbase_ds"
$include "matchmaking_base_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DS_DLL;SWDS"
}
}
$Configuration "Debug"
{
$General
{
$IntermediateDirectory ".\Debug_matchmakingbase_ds$PLATSUBDIR" [$WINDOWS||$POSIX]
$IntermediateDirectory ".\Debug_matchmakingbase_ds_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$IntermediateDirectory ".\Release_matchmakingbase_ds$PLATSUBDIR" [$WINDOWS||$POSIX]
$IntermediateDirectory ".\Release_matchmakingbase_ds_360" [$X360]
}
}
$Project "matchmakingbase_ds"
{
$Folder "Matchmaking"
{
-$File "mm_session.cpp"
}
$Folder "Sessions"
{
-$File "ds_searcher.h"
-$File "ds_searcher.cpp"
-$File "match_searcher.h"
-$File "match_searcher.cpp"
-$File "mm_session_offline_custom.h"
-$File "mm_session_offline_custom.cpp"
-$File "mm_session_online_client.h"
-$File "mm_session_online_client.cpp"
-$File "mm_session_online_host.h"
-$File "mm_session_online_host.cpp"
-$File "mm_session_online_search.h"
-$File "mm_session_online_search.cpp"
-$File "mm_session_online_teamsearch.h"
-$File "mm_session_online_teamsearch.cpp"
-$File "sys_session.h"
-$File "sys_session.cpp"
}
$Folder "Platform - Steam"
{
}
$Folder "Platform - Xbox 360"
{
}
$Folder "Systems"
{
$Folder "Headers"
{
-$File "leaderboards.h"
-$File "player.h"
-$File "searchmanager.h"
}
-$File "leaderboards.cpp"
-$File "player.cpp"
-$File "searchmanager.cpp"
}
$Folder "Utils"
{
}
$Folder "Public Headers"
{
}
$Folder "Source Files"
{
}
}

View File

@@ -0,0 +1,13 @@
"vpc_cache"
{
"CacheVersion" "1"
"win32"
{
"CRCFile" "matchmakingbase_ds.vcxproj.vpc_crc"
"OutputFiles"
{
"0" "matchmakingbase_ds.vcxproj"
"1" "matchmakingbase_ds.vcxproj.filters"
}
}
}

View File

@@ -0,0 +1,149 @@
//-----------------------------------------------------------------------------
// matchmaking_base_inc.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro SRCDIR ".."
$Include "$SRCDIR\vpc_scripts\source_lib_base.vpc"
$Configuration
{
$Compiler
{
$AdditionalIncludeDirectories "$BASE,..\common,$SRCDIR\gcsdk\steamextra,$SRCDIR\game\shared,$SRCDIR\thirdparty\protobuf-2.5.0\src"
$PreprocessorDefinitions "$BASE;NO_STRING_T;VECTOR;VERSION_SAFE_STEAM_API_INTERFACES;PROTECTED_THINGS_ENABLE;NO_STEAM_GAMECOORDINATOR"
$PreprocessorDefinitions "$BASE;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead" [!$PS3]
}
}
$Project
{
$Folder "Matchmaking"
{
$Folder "Headers"
{
$File "matchmakingqos.h"
$File "mm_framework.h"
$File "mm_netmsgcontroller.h"
$File "mm_events.h"
$File "mm_extensions.h"
$File "mm_session.h"
$File "mm_title_main.h"
$File "mm_voice.h"
}
$File "matchmakingqos.cpp"
$File "mm_events.cpp"
$File "mm_extensions.cpp"
$File "mm_framework.cpp"
$File "mm_netmsgcontroller.cpp"
$File "mm_session.cpp"
$File "mm_voice.cpp"
}
$Folder "Sessions"
{
$File "ds_searcher.h"
$File "ds_searcher.cpp"
$File "match_searcher.h"
$File "match_searcher.cpp"
$File "mm_netmgr.h"
$File "mm_netmgr.cpp"
$File "mm_session_offline_custom.h"
$File "mm_session_offline_custom.cpp"
$File "mm_session_online_client.h"
$File "mm_session_online_client.cpp"
$File "mm_session_online_host.h"
$File "mm_session_online_host.cpp"
$File "mm_session_online_search.h"
$File "mm_session_online_search.cpp"
$File "mm_session_online_teamsearch.h"
$File "mm_session_online_teamsearch.cpp"
$File "sys_session.h"
$File "sys_session.cpp"
}
$Folder "Platform - Steam"
{
$File "steam_apihook.cpp"
$File "steam_apihook.h"
$File "steam_datacenterjobs.h"
$File "steam_datacenterjobs.cpp" [!$X360]
$File "steam_lobbyapi.cpp" [!$NO_STEAM && !$DEDICATED && !$X360]
$File "steam_lobbyapi.h"
}
$Folder "Platform - Xbox 360"
{
$File "x360_lobbyapi.cpp" [$X360]
$File "x360_lobbyapi.h"
$File "x360_netmgr.cpp" [$X360]
$File "x360_netmgr.h"
$File "x360_xlsp_cmd.cpp"
$File "x360_xlsp_cmd.h"
}
$Folder "Systems"
{
$Folder "Headers"
{
$File "datacenter.h"
$File "leaderboards.h"
$File "matchsystem.h"
$File "mm_dlc.h"
$File "player.h"
$File "playermanager.h"
$File "searchmanager.h"
$File "servermanager.h"
$File "playerrankingdata.h"
}
$File "datacenter.cpp"
$File "mm_dlc.cpp"
$File "leaderboards.cpp"
$File "matchsystem.cpp"
$File "player.cpp"
$File "playermanager.cpp"
$File "searchmanager.cpp"
$File "servermanager.cpp"
$File "playerrankingdata.cpp"
}
$Folder "Utils"
{
$File "extkeyvalues.h"
$File "extkeyvalues.cpp"
}
$Folder "Public Headers"
{
$File "$SRCDIR\public\matchmaking\idatacenter.h"
$File "$SRCDIR\public\matchmaking\imatchasync.h"
$File "$SRCDIR\public\matchmaking\imatchextensions.h"
$File "$SRCDIR\public\matchmaking\imatchevents.h"
$File "$SRCDIR\public\matchmaking\imatchframework.h"
$File "$SRCDIR\public\matchmaking\imatchtitle.h"
$File "$SRCDIR\public\matchmaking\imatchnetworkmsg.h"
$File "$SRCDIR\public\matchmaking\imatchsystem.h"
$File "$SRCDIR\public\matchmaking\imatchvoice.h"
$File "$SRCDIR\public\matchmaking\iplayer.h"
$File "$SRCDIR\public\matchmaking\iplayermanager.h"
$File "$SRCDIR\public\matchmaking\isearchmanager.h"
$File "$SRCDIR\public\matchmaking\iservermanager.h"
$File "$SRCDIR\public\matchmaking\iplayerrankingdata.h"
}
$Folder "Source Files"
{
$File "$SRCDIR\public\filesystem_helpers.cpp"
$File "main.cpp"
}
$Folder "Link Libraries"
{
$Lib gcsdk [!$X360 && !$PS3]
}
}

View File

@@ -0,0 +1,55 @@
//-----------------------------------------------------------------------------
// matchmaking_cstrike15.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking"
$Include "matchmaking_cstrike15_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DLL"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME$PLATSUBDIR" [$WINDOWS||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME$PLATSUBDIR" [$WINDOWS||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_360" [$X360]
$OutputDirectory ".\Debug_$GAMENAME_PS3" [$PS3]
$IntermediateDirectory ".\Debug_$GAMENAME_PS3" [$PS3]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME$PLATSUBDIR" [$WINDOWS||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME$PLATSUBDIR" [$WINDOWS||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_360" [$X360]
$OutputDirectory ".\Release_$GAMENAME_PS3" [$PS3]
$IntermediateDirectory ".\Release_$GAMENAME_PS3" [$PS3]
}
}
$Project "Matchmaking (CSGO)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase
}
}

View File

@@ -0,0 +1,13 @@
"vpc_cache"
{
"CacheVersion" "1"
"win32"
{
"CRCFile" "matchmaking_csgo.vcxproj.vpc_crc"
"OutputFiles"
{
"0" "matchmaking_csgo.vcxproj"
"1" "matchmaking_csgo.vcxproj.filters"
}
}
}

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// matchmaking_cstrike15_ds.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking_ds"
$Include "matchmaking_cstrike15_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DS_DLL;SWDS"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME_DS$PLATSUBDIR" [$WINDOWS||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME_DS$PLATSUBDIR" [$WINDOWS||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME_DS$PLATSUBDIR" [$WINDOWS||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME_DS$PLATSUBDIR" [$WINDOWS||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_DS_360" [$X360]
}
}
$Project "Matchmaking_DS (CSGO)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase_ds
}
}

View File

@@ -0,0 +1,13 @@
"vpc_cache"
{
"CacheVersion" "1"
"win32"
{
"CRCFile" "matchmaking_ds_csgo.vcxproj.vpc_crc"
"OutputFiles"
{
"0" "matchmaking_ds_csgo.vcxproj"
"1" "matchmaking_ds_csgo.vcxproj.filters"
}
}
}

View File

@@ -0,0 +1,40 @@
//-----------------------------------------------------------------------------
// matchmaking_cstrike15_inc.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro SRCDIR ".."
$Macro GAMENAME "csgo"
$Macro GAMESUBDIR "cstrike15"
$Macro GENERATED_PROTO_DIR "generated_proto"
$Macro OUTBINDIR "$SRCDIR\..\game\$GAMENAME\bin"
$Macro DEVKITBINDIR "$GAMENAME\bin" [$X360]
$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
$Include "matchmaking_inc.vpc"
$Project
{
$Folder "cstrike15"
{
$File "$GAMESUBDIR/mm_title.h"
$File "$GAMESUBDIR/mm_title.cpp"
$File "$GAMESUBDIR/mm_title_gamesettingsmgr.cpp"
$File "$GAMESUBDIR/mm_title_main.cpp"
$File "$GAMESUBDIR/mm_title_richpresence.h"
$File "$GAMESUBDIR/mm_title_richpresence.cpp"
$File "$GAMESUBDIR/mm_title_titledata.cpp"
$File "$GAMESUBDIR/mm_title_contextvalues.h"
}
$Folder "Shared"
{
$File "$SRCDIR/game/shared/cstrike15/gametypes.h"
$File "$SRCDIR/game/shared/cstrike15/gametypes.cpp"
$File "$SRCDIR/public/gametypes/igametypes.h"
$File "$SRCDIR/common/platforminputdevice.h"
$File "$SRCDIR/common/platforminputdevice.cpp"
}
}

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// matchmaking_dota.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking"
$Include "matchmaking_dota_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DLL"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME" [$WIN32||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME" [$WIN32||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME" [$WIN32||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME" [$WIN32||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_360" [$X360]
}
}
$Project "Matchmaking (DOTA)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase
}
}

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// matchmaking_dota_ds.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking_ds"
$Include "matchmaking_dota_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DS_DLL;SWDS"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME_DS" [$WIN32||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME_DS" [$WIN32||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME_DS" [$WIN32||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME_DS" [$WIN32||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_DS_360" [$X360]
}
}
$Project "Matchmaking_DS (DOTA)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase_ds
}
}

View File

@@ -0,0 +1,29 @@
//-----------------------------------------------------------------------------
// matchmaking_dota_inc.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro SRCDIR ".."
$Macro GAMENAME "dota"
$Macro GAMESUBDIR "dota"
$Macro OUTBINDIR "$SRCDIR\..\game\$GAMENAME\bin"
$Macro DEVKITBINDIR "$GAMENAME\bin" [$X360]
$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
$Include "matchmaking_inc.vpc"
$Project
{
$Folder "DOTA"
{
$File "$GAMESUBDIR/mm_title.h"
$File "$GAMESUBDIR/mm_title.cpp"
$File "$GAMESUBDIR/mm_title_gamesettingsmgr.cpp"
$File "$GAMESUBDIR/mm_title_main.cpp"
$File "$GAMESUBDIR/mm_title_richpresence.h"
$File "$GAMESUBDIR/mm_title_richpresence.cpp"
$File "$GAMESUBDIR/mm_title_titledata.cpp"
}
}

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// matchmaking_episodic.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking"
$Include "matchmaking_episodic_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DLL"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME" [$WIN32||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME" [$WIN32||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME" [$WIN32||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME" [$WIN32||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_360" [$X360]
}
}
$Project "Matchmaking (episodic)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase
}
}

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// matchmaking_episodic_ds.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking_ds"
$Include "matchmaking_episodic_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DS_DLL;SWDS"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME_DS" [$WIN32||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME_DS" [$WIN32||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME_DS" [$WIN32||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME_DS" [$WIN32||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_DS_360" [$X360]
}
}
$Project "Matchmaking_DS (episodic)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase_ds
}
}

View File

@@ -0,0 +1,29 @@
//-----------------------------------------------------------------------------
// matchmaking_episodic_inc.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro SRCDIR ".."
$Macro GAMENAME "episodic"
$Macro GAMESUBDIR "episodic"
$Macro OUTBINDIR "$SRCDIR\..\game\$GAMENAME\bin"
$Macro DEVKITBINDIR "$GAMENAME\bin" [$X360]
$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
$Include "matchmaking_inc.vpc"
$Project
{
$Folder "episodic"
{
$File "$GAMESUBDIR/mm_title.h"
$File "$GAMESUBDIR/mm_title.cpp"
$File "$GAMESUBDIR/mm_title_gamesettingsmgr.cpp"
$File "$GAMESUBDIR/mm_title_main.cpp"
$File "$GAMESUBDIR/mm_title_richpresence.h"
$File "$GAMESUBDIR/mm_title_richpresence.cpp"
$File "$GAMESUBDIR/mm_title_titledata.cpp"
}
}

View File

@@ -0,0 +1,71 @@
$Configuration
{
$Compiler
{
$AdditionalIncludeDirectories "$BASE,$SRCDIR\gcsdk\steamextra,..\common,$SRCDIR\thirdparty\protobuf-2.5.0\src"
$AdditionalIncludeDirectories "$BASE,..\common\xlast_$GAMENAME" [!$DEMO]
$AdditionalIncludeDirectories "$BASE,..\common\xlast_$GAMENAME_demo" [$DEMO]
$PreprocessorDefinitions "$BASE;NO_STRING_T;VECTOR;VERSION_SAFE_STEAM_API_INTERFACES;PROTECTED_THINGS_ENABLE;NO_STEAM_GAMECOORDINATOR"
$PreprocessorDefinitions "$BASE;strncpy=use_Q_strncpy_instead;_snprintf=use_Q_snprintf_instead" [!$PS3]
}
$Linker
{
$AdditionalLibraryDirectories "$BASE;$(XEDK)\lib\win32\vs2010" [$XDKINSTALLED]
$SystemLibraries "iconv" [$OSXALL]
}
}
$Configuration
{
$Linker [$WINDOWS]
{
$AdditionalDependencies "$BASE wsock32.lib ws2_32.lib wininet.lib"
}
$Linker [$PS3]
{
$AdditionalDependencies "$BASE libnet_stub.a libnetctl_stub.a"
}
}
$Configuration "Debug"
{
$Linker [$X360]
{
$AdditionalDependencies "$BASE xaudiod2.lib Xhvd2.lib xmpd.lib"
}
$Linker [$WINDOWS]
{
$AdditionalDependencies "$BASE wsock32.lib ws2_32.lib wininet.lib"
}
}
$Configuration "Release"
{
$Linker [$X360]
{
$AdditionalDependencies "$BASE xaudio2.lib Xhv2.lib xmp.lib"
}
$Linker [$WINDOWS]
{
$AdditionalDependencies "$BASE wsock32.lib ws2_32.lib wininet.lib"
}
}
$Project
{
$Folder "Link Libraries"
{
$Lib mathlib
$Lib tier2
$Lib tier3
$ImplibExternal steam_api [ ( $WIN32 || $POSIX || $PS3 ) && !$NO_STEAM ]
$ImplibExternal steam_api64 [ $WIN64 && !$NO_STEAM ]
$Lib gcsdk [!$X360 && !$PS3]
}
}

View File

@@ -0,0 +1,55 @@
//-----------------------------------------------------------------------------
// matchmaking_portal2.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking"
$Include "matchmaking_portal2_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DLL"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME" [$WIN32||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME" [$WIN32||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_360" [$X360]
$OutputDirectory ".\Debug_$GAMENAME_PS3" [$PS3]
$IntermediateDirectory ".\Debug_$GAMENAME_PS3" [$PS3]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME" [$WIN32||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME" [$WIN32||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_360" [$X360]
$OutputDirectory ".\Release_$GAMENAME_PS3" [$PS3]
$IntermediateDirectory ".\Release_$GAMENAME_PS3" [$PS3]
}
}
$Project "Matchmaking (portal2)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase
}
}

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// matchmaking_portal2_ds.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking_ds"
$Include "matchmaking_portal2_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DS_DLL;SWDS"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME_DS" [$WIN32||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME_DS" [$WIN32||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME_DS" [$WIN32||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME_DS" [$WIN32||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_DS_360" [$X360]
}
}
$Project "Matchmaking_DS (portal2)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase_ds
}
}

View File

@@ -0,0 +1,29 @@
//-----------------------------------------------------------------------------
// matchmaking_portal2_inc.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro SRCDIR ".."
$Macro GAMENAME "portal2"
$Macro GAMESUBDIR "portal2"
$Macro OUTBINDIR "$SRCDIR\..\game\$GAMENAME\bin"
$Macro DEVKITBINDIR "$GAMENAME\bin" [$X360]
$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
$Include "matchmaking_inc.vpc"
$Project
{
$Folder "portal2"
{
$File "$GAMESUBDIR/mm_title.h"
$File "$GAMESUBDIR/mm_title.cpp"
$File "$GAMESUBDIR/mm_title_gamesettingsmgr.cpp"
$File "$GAMESUBDIR/mm_title_main.cpp"
$File "$GAMESUBDIR/mm_title_richpresence.h"
$File "$GAMESUBDIR/mm_title_richpresence.cpp"
$File "$GAMESUBDIR/mm_title_titledata.cpp"
}
}

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// matchmaking_swarm.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking"
$Include "matchmaking_swarm_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DLL"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME" [$WIN32||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME" [$WIN32||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME" [$WIN32||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME" [$WIN32||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_360" [$X360]
}
}
$Project "Matchmaking (Swarm)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase
}
}

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// matchmaking_swarm_ds.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking_ds"
$Include "matchmaking_swarm_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DS_DLL;SWDS"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME_DS" [$WIN32||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME_DS" [$WIN32||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME_DS" [$WIN32||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME_DS" [$WIN32||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_DS_360" [$X360]
}
}
$Project "Matchmaking_DS (Swarm)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase_ds
}
}

View File

@@ -0,0 +1,31 @@
//-----------------------------------------------------------------------------
// matchmaking_swarm_inc.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro SRCDIR ".."
$Macro GAMENAME "swarm"
$Macro GAMESUBDIR "swarm"
$Macro OUTBINDIR "$SRCDIR\..\game\$GAMENAME\bin"
$Macro DEVKITBINDIR "$GAMENAME\bin" [$X360]
$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
$Include "matchmaking_inc.vpc"
$Project
{
$Folder "Swarm"
{
$File "$GAMESUBDIR/matchext_swarm.h"
$File "$GAMESUBDIR/matchext_swarm.cpp"
$File "$GAMESUBDIR/mm_title.h"
$File "$GAMESUBDIR/mm_title.cpp"
$File "$GAMESUBDIR/mm_title_gamesettingsmgr.cpp"
$File "$GAMESUBDIR/mm_title_main.cpp"
$File "$GAMESUBDIR/mm_title_richpresence.h"
$File "$GAMESUBDIR/mm_title_richpresence.cpp"
$File "$GAMESUBDIR/mm_title_titledata.cpp"
}
}

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// matchmaking_tf.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking"
$Include "matchmaking_tf_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DLL"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME" [$WIN32||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME" [$WIN32||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME" [$WIN32||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME" [$WIN32||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_360" [$X360]
}
}
$Project "Matchmaking (tf)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase
}
}

View File

@@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// matchmaking_tf_ds.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro OUTBINNAME "matchmaking_ds"
$Include "matchmaking_tf_inc.vpc"
$Configuration
{
$Compiler
{
$PreprocessorDefinitions "$BASE;MATCHMAKING_DS_DLL;SWDS"
}
}
$Configuration "Debug"
{
$General
{
$OutputDirectory ".\Debug_$GAMENAME_DS" [$WIN32||$POSIX]
$IntermediateDirectory ".\Debug_$GAMENAME_DS" [$WIN32||$POSIX]
$OutputDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Debug_$GAMENAME_DS_360" [$X360]
}
}
$Configuration "Release"
{
$General
{
$OutputDirectory ".\Release_$GAMENAME_DS" [$WIN32||$POSIX]
$IntermediateDirectory ".\Release_$GAMENAME_DS" [$WIN32||$POSIX]
$OutputDirectory ".\Release_$GAMENAME_DS_360" [$X360]
$IntermediateDirectory ".\Release_$GAMENAME_DS_360" [$X360]
}
}
$Project "Matchmaking_DS (tf)"
{
$Folder "Link Libraries"
{
$Lib matchmakingbase_ds
}
}

View File

@@ -0,0 +1,29 @@
//-----------------------------------------------------------------------------
// matchmaking_tf_inc.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$Macro SRCDIR ".."
$Macro GAMENAME "tf"
$Macro GAMESUBDIR "tf"
$Macro OUTBINDIR "$SRCDIR\..\game\$GAMENAME\bin"
$Macro DEVKITBINDIR "$GAMENAME\bin" [$X360]
$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
$Include "matchmaking_inc.vpc"
$Project
{
$Folder "tf"
{
$File "$GAMESUBDIR/mm_title.h"
$File "$GAMESUBDIR/mm_title.cpp"
$File "$GAMESUBDIR/mm_title_gamesettingsmgr.cpp"
$File "$GAMESUBDIR/mm_title_main.cpp"
$File "$GAMESUBDIR/mm_title_richpresence.h"
$File "$GAMESUBDIR/mm_title_richpresence.cpp"
$File "$GAMESUBDIR/mm_title_titledata.cpp"
}
}

View File

@@ -0,0 +1,262 @@
//====== Copyright c 1996-2007, Valve Corporation, All rights reserved. =======//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "matchmakingqos.h"
#include "threadtools.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#if defined( _X360 )
//-----------------------------------------------------------------------------
// Quality of Service detection routines
//-----------------------------------------------------------------------------
class CQosDetector
{
public:
CQosDetector();
~CQosDetector();
public:
bool InitiateLookup();
bool WaitForResults();
public:
MM_QOS_t GetResults() const { return m_Results; }
private:
void Release();
XNQOS *m_pQos;
void ProcessResults();
MM_QOS_t m_Results;
};
CQosDetector::CQosDetector() : m_pQos( NULL )
{
m_Results.nPingMsMin = 50; // 50 ms default ping
m_Results.nPingMsMed = 50; // 50 ms default ping
m_Results.flBwUpKbs = 32.0f; // 32 KB/s = 256 kbps
m_Results.flBwDnKbs = 32.0f; // 32 KB/s = 256 kbps
m_Results.flLoss = 0.0f; // 0% packet loss
}
CQosDetector::~CQosDetector()
{
Release();
}
void CQosDetector::Release()
{
if ( m_pQos )
{
g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pQos );
m_pQos = NULL;
}
}
bool CQosDetector::InitiateLookup()
{
Release();
//
// Issue the asynchronous QOS service lookup query
//
XNQOS *pQos = NULL;
INT err = g_pMatchExtensions->GetIXOnline()->XNetQosServiceLookup( NULL, NULL, &pQos );
if ( err )
{
Msg( "CQosDetector::InitiateLookup failed, err = %d\n", err );
return false;
}
if ( !pQos )
{
Msg( "CQosDetector::InitiateLookup failed, qos is null.\n" );
return false;
}
m_pQos = pQos;
return true;
}
bool CQosDetector::WaitForResults()
{
if ( !m_pQos )
return false;
//
// Spin-sleep here while waiting for the probes to come back
//
const int numSecQosTimeout = 30; // Consider QOS query timed out if 30 seconds elapsed
float flTimeStart = Plat_FloatTime(), flTimeEndTimeout = flTimeStart + numSecQosTimeout;
for ( ; ; )
{
if ( Plat_FloatTime() > flTimeEndTimeout )
{
Msg( "CQosDetector::WaitForResults timed out after %d sec.\n", numSecQosTimeout );
Release();
return false; // Timed out
}
if ( m_pQos->axnqosinfo->bFlags & XNET_XNQOSINFO_COMPLETE )
break; // QOS query has completed
ThreadSleep( 10 );
}
if ( ! ( m_pQos->axnqosinfo->bFlags & XNET_XNQOSINFO_TARGET_CONTACTED ) ||
( m_pQos->axnqosinfo->bFlags & XNET_XNQOSINFO_TARGET_DISABLED ) )
{
// Failed to contact host or target disabled
Msg( "CQosDetector::WaitForResults host unavailable.\n" );
Release();
return false;
}
ProcessResults();
Release();
return true;
}
void CQosDetector::ProcessResults()
{
MM_QOS_t Results;
Results.nPingMsMin = m_pQos->axnqosinfo->wRttMinInMsecs;
Results.nPingMsMed = m_pQos->axnqosinfo->wRttMedInMsecs;
Results.flBwUpKbs = m_pQos->axnqosinfo->dwUpBitsPerSec / 8192.0f;
if ( Results.flBwUpKbs < 1.f )
{
Results.flBwUpKbs = 1.f;
}
Results.flBwDnKbs = m_pQos->axnqosinfo->dwDnBitsPerSec / 8192.0f;
if ( Results.flBwDnKbs < 1.f )
{
Results.flBwDnKbs = 1.f;
}
Results.flLoss = m_pQos->axnqosinfo->cProbesXmit ? ( float( m_pQos->axnqosinfo->cProbesXmit - m_pQos->axnqosinfo->cProbesRecv ) * 100.f / m_pQos->axnqosinfo->cProbesXmit ) : 0.f;
m_Results = Results;
// Dump the results
Msg(
"[QOS] Fresh QOS results available:\n"
"[QOS] ping %d min, %d med\n"
"[QOS] bandwidth %.1f kB/s upstream, %.1f kB/s downstream\n"
"[QOS] avg packet loss %.0f percents\n",
Results.nPingMsMin, Results.nPingMsMed,
Results.flBwUpKbs, Results.flBwDnKbs,
Results.flLoss
);
}
//
// Global QOS detector thread
//
static class CQosThread
{
public:
CQosThread();
MM_QOS_t GetResults();
static unsigned ThreadProc( void *pThis ) { return ( (CQosThread *) pThis )->Run(), 0; }
void Run();
private:
ThreadHandle_t m_hHandle;
CThreadEvent m_hRequestResultsEvent; // Auto reset event to trigger next query
CQosDetector m_QosDetector;
}
s_QosThread;
CQosThread::CQosThread() : m_hHandle( NULL )
{
}
void CQosThread::Run()
{
//
// Sit here and fetch fresh QOS results whenever somebody needs them.
// Every request of QOS data is instantaneous and returns the last successful QOS query result.
//
for ( unsigned int numQosRequestsMade = 0; ; ++ numQosRequestsMade )
{
m_QosDetector.InitiateLookup();
m_QosDetector.WaitForResults();
if ( numQosRequestsMade )
{
m_hRequestResultsEvent.Wait();
}
}
ReleaseThreadHandle( m_hHandle );
m_hHandle = NULL;
}
MM_QOS_t CQosThread::GetResults()
{
// Launch the thread if this is the first time QOS is needed
if ( !m_hHandle )
{
m_hHandle = CreateSimpleThread( ThreadProc, this );
if( m_hHandle )
{
ThreadSetAffinity( m_hHandle, XBOX_PROCESSOR_3 );
}
}
// Signal the event that we can make a fresh QOS query
m_hRequestResultsEvent.Set();
return m_QosDetector.GetResults();
}
//
// Function to retrieve the result of the last QOS query
//
MM_QOS_t MM_GetQos()
{
return s_QosThread.GetResults();
}
#else
//
// Default implementation of QOS
//
static struct Default_MM_QOS_t : public MM_QOS_t
{
Default_MM_QOS_t()
{
nPingMsMin = 50; // 50 ms default ping
nPingMsMed = 50; // 50 ms default ping
flBwUpKbs = 32.0f; // 32 KB/s = 256 kbps
flBwDnKbs = 32.0f; // 32 KB/s = 256 kbps
flLoss = 0.0f; // 0% packet loss
}
}
s_DefaultQos;
MM_QOS_t MM_GetQos()
{
return s_DefaultQos;
}
#endif

View File

@@ -0,0 +1,19 @@
//====== Copyright c 1996-2007, Valve Corporation, All rights reserved. =======//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#ifndef MATCHMAKING_QOS_H
#define MATCHMAKING_QOS_H
#ifdef _WIN32
#pragma once
#endif
#include "mm_framework.h"
MM_QOS_t MM_GetQos();
#endif // #ifndef MATCHMAKING_QOS_H

106
matchmaking/matchsystem.cpp Normal file
View File

@@ -0,0 +1,106 @@
//========= Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#include "mm_framework.h"
#ifdef _X360
#include "xonline.h"
#else
#include "xbox/xboxstubs.h"
#endif
#include "matchsystem.h"
#include "playermanager.h"
#include "servermanager.h"
#include "datacenter.h"
#ifndef SWDS
#include "searchmanager.h"
#include "leaderboards.h"
#endif
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
static CMatchSystem s_MatchSystem;
CMatchSystem *g_pMatchSystem = &s_MatchSystem;
bool IsValidXNKID( XNKID xnkid )
{
for(int i = 0; i < 8; ++i)
{
if(xnkid.ab[i])
return true;
}
return false;
}
CMatchSystem::CMatchSystem()
{
}
CMatchSystem::~CMatchSystem()
{
}
IPlayerManager * CMatchSystem::GetPlayerManager()
{
return g_pPlayerManager;
}
IMatchVoice * CMatchSystem::GetMatchVoice()
{
return g_pMatchVoice;
}
IServerManager * CMatchSystem::GetUserGroupsServerManager()
{
return g_pServerManager;
}
ISearchManager * CMatchSystem::CreateGameSearchManager( KeyValues *pParams )
{
#ifndef SWDS
return new CSearchManager( pParams );
#else
return NULL;
#endif
}
IDatacenter * CMatchSystem::GetDatacenter()
{
return g_pDatacenter;
}
IDlcManager * CMatchSystem::GetDlcManager()
{
return g_pDlcManager;
}
void CMatchSystem::Update()
{
if ( g_pPlayerManager )
g_pPlayerManager->Update();
if ( g_pServerManager )
g_pServerManager->Update();
#ifndef SWDS
CSearchManager::UpdateAll();
if ( g_pLeaderboardRequestQueue )
g_pLeaderboardRequestQueue->Update();
#endif
if ( g_pDatacenter )
g_pDatacenter->Update();
if ( g_pDlcManager )
g_pDlcManager->Update();
}

49
matchmaking/matchsystem.h Normal file
View File

@@ -0,0 +1,49 @@
//========= Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef _MATCHSYSTEM_H_
#define _MATCHSYSTEM_H_
#include "mm_framework.h"
#include "utlvector.h"
enum PacketTargetType
{
PTT_PLAYER = 0,
PTT_GAME,
};
bool IsValidXNKID( XNKID xnkid );
class CMatchSystem : public IMatchSystem
{
public:
CMatchSystem();
~CMatchSystem();
public:
void Update();
//IMatchSystem
public:
virtual IPlayerManager * GetPlayerManager();
virtual IMatchVoice * GetMatchVoice();
virtual IServerManager * GetUserGroupsServerManager();
virtual ISearchManager * CreateGameSearchManager( KeyValues *pParams );
virtual IDatacenter * GetDatacenter();
virtual IDlcManager * GetDlcManager();
};
extern CMatchSystem *g_pMatchSystem;
#endif

340
matchmaking/mm_dlc.cpp Normal file
View File

@@ -0,0 +1,340 @@
//========= Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef _X360
#include "xbox/xboxstubs.h"
#endif
#include "mm_framework.h"
#include "filesystem.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
static CDlcManager g_DlcManager;
CDlcManager *g_pDlcManager = &g_DlcManager;
CON_COMMAND( mm_dlc_debugprint, "Shows information about dlc" )
{
KeyValuesDumpAsDevMsg( g_pDlcManager->GetDataInfo(), 1 );
}
//////////////////////////////////////////////////////////////////////////
CDlcManager::CDlcManager() :
m_pDataInfo( NULL ),
m_eState( STATE_IDLE ),
m_flTimestamp( 0.0f ),
m_bNeedToDiscoverAllDlcs( true ),
m_bNeedToUpdateFileSystem( false )
{
#ifdef _X360
m_hEnumerator = NULL;
memset( &m_xOverlapped, 0, sizeof( m_xOverlapped ) );
#endif
}
CDlcManager::~CDlcManager()
{
if ( m_pDataInfo )
m_pDataInfo->deleteThis();
m_pDataInfo = NULL;
}
void CDlcManager::Update()
{
#ifdef _X360
// Per TCR-126 we don't want to open DLC for users who haven't unlocked the game yet
IXboxSystem *pXboxSystem = g_pMatchExtensions->GetIXboxSystem();
if ( !pXboxSystem || !pXboxSystem->IsArcadeTitleUnlocked() )
{
return;
}
DWORD ret = 0;
switch ( m_eState )
{
case STATE_XENUMERATE:
if ( !XHasOverlappedIoCompleted( &m_xOverlapped ) )
return;
ret = XGetOverlappedResult( &m_xOverlapped, ( DWORD * ) &m_dwNumItems, false );
if ( ret != ERROR_SUCCESS )
{
Warning( "DLCMANAGER: XContentCreateEnumerator/XEnumerate async failed with error %d!\n", ret );
m_dwNumItems = 0;
m_bNeedToDiscoverAllDlcs = true;
}
CloseHandle( m_hEnumerator );
m_hEnumerator = NULL;
CreateNextContent();
return;
case STATE_XCONTENT_CREATE:
if ( !XHasOverlappedIoCompleted( &m_xOverlapped ) )
return;
ProcessNextContent();
return;
}
#endif
// Once we are idle check if we need to update the file systems list of DLC
if ( m_eState == STATE_IDLE )
{
if ( m_bNeedToUpdateFileSystem )
{
m_bNeedToUpdateFileSystem = false;
g_pFullFileSystem->DiscoverDLC( XBX_GetPrimaryUserId() );
}
}
}
void CDlcManager::RequestDlcUpdate()
{
if ( m_eState > STATE_IDLE )
return;
if ( m_eState == STATE_IDLE && !m_bNeedToDiscoverAllDlcs )
{
Msg( "DLCMANAGER: RequestDlcUpdate has no new content.\n" );
return;
}
// If we specified dlc via the command line we can skip the actual enumeration
const char *pCmdLine = CommandLine()->GetCmdLine();
if ( V_stristr( pCmdLine, "-dlc" ) )
{
m_eState = STATE_IDLE;
m_bNeedToUpdateFileSystem = true;
m_bNeedToDiscoverAllDlcs = false;
return;
}
#if !defined( NO_STEAM ) && !defined( SWDS )
// Client is requesting a DLC update
m_CallbackOnDLCInstalled.Register( this, &CDlcManager::Steam_OnDLCInstalled );
Steam_OnDLCInstalled( NULL );
#endif
#ifdef _X360
if ( XBX_GetPrimaryUserIsGuest() )
{
Msg( "DLCMANAGER: RequestDlcUpdate will not update for guests.\n" );
return;
}
DWORD nBufferSize = 0;
DWORD ret = XContentCreateEnumerator( XBX_GetPrimaryUserId(), XCONTENTDEVICE_ANY,
XCONTENTTYPE_MARKETPLACE, 0, 100, &nBufferSize, &m_hEnumerator );
if ( ret )
{
Warning( "DLCMANAGER: XContentCreateEnumerator failed with error %d!\n", ret );
return;
}
if ( nBufferSize )
{
m_dwNumItems = 0;
m_arrContentData.EnsureCapacity( nBufferSize/sizeof( XCONTENT_DATA ) + 1 );
m_arrContentData.RemoveAll();
ret = XEnumerate( m_hEnumerator, m_arrContentData.Base(), nBufferSize, NULL, &m_xOverlapped );
if ( ret && ret != ERROR_IO_PENDING )
{
Warning( "DLCMANAGER: XContentCreateEnumerator/XEnumerate failed with error %d!\n", ret );
}
Msg( "DLCMANAGER: XContentCreateEnumerator/XEnumerate initiated.\n" );
m_eState = STATE_XENUMERATE;
m_flTimestamp = Plat_FloatTime();
m_bNeedToDiscoverAllDlcs = false;
return;
}
Warning( "DLCMANAGER: XContentCreateEnumerator not starting enumeration!\n" );
::CloseHandle( m_hEnumerator );
m_hEnumerator = NULL;
#endif
}
#ifdef _X360
void CDlcManager::CreateNextContent()
{
Msg( "DLCMANAGER: enumeration checkpoint after %.3f sec\n", Plat_FloatTime() - m_flTimestamp );
while ( m_dwNumItems -- > 0 )
{
XCONTENT_DATA *pContentData = m_arrContentData.Base() + m_dwNumItems;
char chKey[ XCONTENT_MAX_FILENAME_LENGTH + 1 ];
memcpy( chKey, pContentData->szFileName, XCONTENT_MAX_FILENAME_LENGTH );
chKey[ XCONTENT_MAX_FILENAME_LENGTH ] = 0;
if ( m_pDataInfo->FindKey( chKey ) )
continue; // Already processed that DLC
// Kick off DLC processing
m_dwLicenseMask = 0;
DWORD ret = XContentCreate( XBX_GetPrimaryUserId(), "PKG", pContentData, XCONTENTFLAG_OPENEXISTING, NULL, &m_dwLicenseMask, &m_xOverlapped );
if ( ret && ( ret != ERROR_IO_PENDING ) )
{
Warning( "DLCMANAGER: [%.*s] is corrupt\n", ARRAYSIZE( pContentData->szFileName ), pContentData->szFileName );
continue; // assume corrupt
}
m_eState = STATE_XCONTENT_CREATE;
return;
}
// All done
m_eState = STATE_IDLE;
float flTime = Plat_FloatTime() - m_flTimestamp;
Msg( "DLCMANAGER: Full update finished after %.3f sec\n", flTime );
KeyValuesDumpAsDevMsg( m_pDataInfo, 1 );
}
void CDlcManager::ProcessNextContent()
{
DWORD dwResult = 0;
DWORD ret = XGetOverlappedResult( &m_xOverlapped, &dwResult, false );
if( ret == ERROR_SUCCESS )
{
XCONTENT_DATA *pContentData = m_arrContentData.Base() + m_dwNumItems;
char chKey[ XCONTENT_MAX_FILENAME_LENGTH + 1 ];
memcpy( chKey, pContentData->szFileName, XCONTENT_MAX_FILENAME_LENGTH );
chKey[ XCONTENT_MAX_FILENAME_LENGTH ] = 0;
Msg( "DLCMANAGER: [%.*s] has license mask = 0x%08X\n", ARRAYSIZE( pContentData->szFileName ), pContentData->szFileName, m_dwLicenseMask );
if ( !m_pDataInfo )
{
m_pDataInfo = new KeyValues( "DlcManager" );
m_pDataInfo->SetUint64( "@info/installed", 0 );
}
if ( KeyValues *pDlc = m_pDataInfo->FindKey( chKey, true ) )
{
pDlc->SetInt( "licensemask", m_dwLicenseMask );
pDlc->SetWString( "displayname", pContentData->szDisplayName );
pDlc->SetInt( "deviceid", pContentData->DeviceID );
}
m_pDataInfo->SetUint64( "@info/installed", m_pDataInfo->GetUint64( "@info/installed" ) | ( 1ull << DLC_LICENSE_ID( m_dwLicenseMask ) ) );
m_bNeedToUpdateFileSystem = true;
}
else
{
XCONTENT_DATA *pContentData = m_arrContentData.Base() + m_dwNumItems;
Warning( "DLCMANAGER: [%.*s] async open failed with error %d\n", ARRAYSIZE( pContentData->szFileName ), pContentData->szFileName, ret );
}
XContentClose( "PKG", NULL );
CreateNextContent();
}
#endif
bool CDlcManager::IsDlcUpdateFinished( bool bWaitForFinish )
{
if ( m_eState == STATE_IDLE )
return true;
if ( !bWaitForFinish )
return false;
float flTimestamp = Plat_FloatTime();
while ( m_eState != STATE_IDLE )
{
Update();
ThreadSleep( 1 );
}
float flEndTimestamp = Plat_FloatTime();
Warning( "DLCMANAGER: Forcing wait for update to finish stalled for %.3f sec\n", flEndTimestamp - flTimestamp );
return true;
}
KeyValues * CDlcManager::GetDataInfo()
{
return m_pDataInfo;
}
void CDlcManager::OnEvent( KeyValues *kvEvent )
{
#ifdef _X360
char const *szEvent = kvEvent->GetName();
if ( !Q_stricmp( "OnDowloadableContentInstalled", szEvent ) )
{
m_bNeedToDiscoverAllDlcs = true;
}
else if ( !Q_stricmp( "OnLiveMembershipPurchased", szEvent ) )
{
m_bNeedToDiscoverAllDlcs = true;
}
else if ( !Q_stricmp( "OnSysSigninChange", szEvent ) )
{
m_bNeedToDiscoverAllDlcs = true;
}
#endif
}
#if !defined( NO_STEAM ) && !defined( SWDS )
void CDlcManager::Steam_OnDLCInstalled( DlcInstalled_t *pParam )
{
m_bNeedToDiscoverAllDlcs = false;
TitleDlcDescription_t const *dlcs = g_pMatchFramework->GetMatchTitle()->DescribeTitleDlcs();
if ( !dlcs )
return;
TitleDataFieldsDescription_t const *fields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage();
uint64 uiOldDlcMask = m_pDataInfo->GetUint64( "@info/installed" );
if ( !m_pDataInfo )
{
m_pDataInfo = new KeyValues( "DlcManager" );
m_pDataInfo->SetUint64( "@info/installed", 0 );
}
IPlayerLocal *pPlayerLocal = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( XBX_GetPrimaryUserId() );
for ( ; dlcs->m_uiLicenseMaskId; ++ dlcs )
{
// Check if DLC already detected
if ( ( uiOldDlcMask & dlcs->m_uiLicenseMaskId ) == dlcs->m_uiLicenseMaskId )
continue;
// Check player profile first
TitleDataFieldsDescription_t const *pDlcField = dlcs->m_szTitleDataBitfieldStatName ?
TitleDataFieldsDescriptionFindByString( fields, dlcs->m_szTitleDataBitfieldStatName ) : NULL;
if ( pDlcField && pPlayerLocal &&
TitleDataFieldsDescriptionGetBit( pDlcField, pPlayerLocal ) )
{
m_pDataInfo->SetUint64( "@info/installed", m_pDataInfo->GetUint64( "@info/installed" ) | dlcs->m_uiLicenseMaskId );
continue;
}
// Check Steam subscription
if ( steamapicontext->SteamApps()->BIsSubscribedApp( dlcs->m_idDlcAppId ) )
{
m_pDataInfo->SetUint64( "@info/installed", m_pDataInfo->GetUint64( "@info/installed" ) | dlcs->m_uiLicenseMaskId );
// Set player profile bit
if ( pDlcField && pPlayerLocal )
TitleDataFieldsDescriptionSetBit( pDlcField, pPlayerLocal, true );
}
}
// Send the event in case detected DLC installed changes
uint64 uiNewDlcMask = m_pDataInfo->GetUint64( "@info/installed" );
if ( uiNewDlcMask != uiOldDlcMask )
{
KeyValues *kvEvent = new KeyValues( "OnDowloadableContentInstalled" );
kvEvent->SetUint64( "installed", uiNewDlcMask );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( kvEvent );
m_bNeedToUpdateFileSystem = true;
}
}
#endif

83
matchmaking/mm_dlc.h Normal file
View File

@@ -0,0 +1,83 @@
//========= Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef _MM_DLC_H_
#define _MM_DLC_H_
#include "utlvector.h"
#include "utlmap.h"
class CDlcManager :
public IDlcManager,
public IMatchEventsSink
{
public :
CDlcManager();
virtual ~CDlcManager();
//
// IDlcManager implementation
//
public:
//
// RequestDlcUpdate
// requests a background DLC update
//
virtual void RequestDlcUpdate();
virtual bool IsDlcUpdateFinished( bool bWaitForFinish = false );
//
// GetDataInfo
// retrieves the last acquired dlc information
//
virtual KeyValues * GetDataInfo();
//
// IMatchEventsSink implementation
//
public:
virtual void OnEvent( KeyValues *kvEvent );
//
// Interface for match system
//
public:
void Update();
protected:
KeyValues *m_pDataInfo;
#ifdef _X360
HANDLE m_hEnumerator;
XOVERLAPPED m_xOverlapped;
int32 m_dwNumItems;
DWORD m_dwLicenseMask;
CUtlVector< XCONTENT_DATA > m_arrContentData;
void CreateNextContent();
void ProcessNextContent();
#endif
#if !defined( NO_STEAM ) && !defined( SWDS )
STEAM_CALLBACK_MANUAL( CDlcManager, Steam_OnDLCInstalled, DlcInstalled_t, m_CallbackOnDLCInstalled );
#endif
enum State_t
{
STATE_IDLE,
#ifdef _X360
STATE_XENUMERATE,
STATE_XCONTENT_CREATE
#endif
};
State_t m_eState;
bool m_bNeedToDiscoverAllDlcs;
bool m_bNeedToUpdateFileSystem;
float m_flTimestamp;
};
extern class CDlcManager *g_pDlcManager;
#endif // _DATACENTER_H_

221
matchmaking/mm_events.cpp Normal file
View File

@@ -0,0 +1,221 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_events.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CMatchEventsSubscription::CMatchEventsSubscription() :
m_bAllowNestedBroadcasts( false ),
m_bBroadcasting( false )
{
;
}
CMatchEventsSubscription::~CMatchEventsSubscription()
{
;
}
static CMatchEventsSubscription g_MatchEventsSubscription;
CMatchEventsSubscription *g_pMatchEventsSubscription = &g_MatchEventsSubscription;
//
// Implementation
//
void CMatchEventsSubscription::Subscribe( IMatchEventsSink *pSink )
{
if ( !pSink )
return;
int idx = m_arrSinks.Find( pSink );
if ( m_arrSinks.IsValidIndex( idx ) )
{
Assert( m_arrRefCount.IsValidIndex( idx ) );
Assert( m_arrRefCount[ idx ] > 0 );
++ m_arrRefCount[ idx ];
}
else
{
m_arrSinks.AddToTail( pSink );
m_arrRefCount.AddToTail( 1 );
Assert( m_arrSinks.Count() == m_arrRefCount.Count() );
}
}
void CMatchEventsSubscription::Unsubscribe( IMatchEventsSink *pSink )
{
if ( !pSink )
return;
int idx = m_arrSinks.Find( pSink );
Assert( m_arrSinks.IsValidIndex( idx ) );
if ( !m_arrSinks.IsValidIndex( idx ) )
return;
Assert( m_arrRefCount.IsValidIndex( idx ) );
Assert( m_arrRefCount[ idx ] > 0 );
-- m_arrRefCount[ idx ];
if ( m_arrRefCount[ idx ] <= 0 )
{
m_arrSinks.Remove( idx );
m_arrRefCount.Remove( idx );
Assert( m_arrSinks.Count() == m_arrRefCount.Count() );
// Update outstanding iterators that are beyond removal point
for ( int k = 0; k < m_arrIteratorsOutstanding.Count(); ++ k )
{
if ( m_arrIteratorsOutstanding[k] >= idx )
-- m_arrIteratorsOutstanding[k];
}
}
}
void CMatchEventsSubscription::Shutdown()
{
m_bBroadcasting = true; // Blocks all BroadcastEvent calls from being dispatched!
}
void CMatchEventsSubscription::BroadcastEvent( KeyValues *pEvent )
{
//
// Network raw packet decryption
//
if ( !Q_stricmp( "OnNetLanConnectionlessPacket", pEvent->GetName() ) )
{
if ( void * pRawPacket = pEvent->GetPtr( "rawpkt" ) )
{
netpacket_t *pkt = ( netpacket_t * ) pRawPacket;
pEvent->deleteThis();
g_pConnectionlessLanMgr->ProcessConnectionlessPacket( pkt );
return;
}
}
//
// Broadcasting events is reliable even when subscribers get added
// or removed during broadcasts, or even broadcast nested events.
//
// Nested broadcasts are not allowed because it messes up the perception
// of event timeline by external listeners, nested broadcast is enqueued
// instead and broadcast after the original event has been broadcast to
// all subscribers.
//
if ( m_bBroadcasting )
{
if ( !m_bAllowNestedBroadcasts )
{
m_arrQueuedEvents.AddToTail( pEvent );
return;
}
}
m_bBroadcasting = true;
KeyValues::AutoDelete autoDeleteEvent( pEvent );
m_arrSentEvents.AddToHead( pEvent );
// Internally events are cracked before external subscribers
g_pMMF->OnEvent( pEvent );
// iterate subscribers
for ( m_arrIteratorsOutstanding.AddToTail( 0 );
m_arrIteratorsOutstanding.Tail() < m_arrSinks.Count();
++ m_arrIteratorsOutstanding.Tail() )
{
int i = m_arrIteratorsOutstanding.Tail();
IMatchEventsSink *pSink = m_arrSinks[ i ];
Assert( m_arrRefCount.IsValidIndex( i ) );
Assert( m_arrRefCount[i] > 0 );
Assert( pSink );
pSink->OnEvent( pEvent );
}
m_arrIteratorsOutstanding.RemoveMultipleFromTail( 1 );
m_bBroadcasting = false;
//
// Broadcast queued events
//
if ( m_arrQueuedEvents.Count() > 0 )
{
KeyValues *pQueued = m_arrQueuedEvents.Head();
m_arrQueuedEvents.RemoveMultipleFromHead( 1 );
BroadcastEvent( pQueued );
return;
}
//
// No more queued events left, clean up registered event data
//
for ( int k = 0; k < m_arrEventData.Count(); ++ k )
{
KeyValues *pRegistered = m_arrEventData[k];
pRegistered->deleteThis();
}
m_arrEventData.Purge();
m_arrSentEvents.Purge();
}
void CMatchEventsSubscription::RegisterEventData( KeyValues *pEventData )
{
Assert( pEventData );
if ( !pEventData )
return;
Assert( m_bBroadcasting );
char const *szEventDataKey = pEventData->GetName();
Assert( szEventDataKey && *szEventDataKey );
if ( !szEventDataKey || !*szEventDataKey )
return;
for ( int k = 0; k < m_arrEventData.Count(); ++ k )
{
KeyValues *&pRegistered = m_arrEventData[k];
if ( !Q_stricmp( szEventDataKey, pRegistered->GetName() ) )
{
pRegistered->deleteThis();
pRegistered = pEventData;
return;
}
}
m_arrEventData.AddToTail( pEventData );
}
KeyValues * CMatchEventsSubscription::GetEventData( char const *szEventDataKey )
{
Assert( m_bBroadcasting );
for ( int k = 0; k < m_arrEventData.Count(); ++ k )
{
KeyValues *pRegistered = m_arrEventData[k];
if ( !Q_stricmp( szEventDataKey, pRegistered->GetName() ) )
return pRegistered;
}
for ( int k = 0; k < m_arrSentEvents.Count(); ++ k )
{
KeyValues *pRegistered = m_arrSentEvents[k];
if ( !Q_stricmp( szEventDataKey, pRegistered->GetName() ) )
return pRegistered;
}
return NULL;
}

51
matchmaking/mm_events.h Normal file
View File

@@ -0,0 +1,51 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_EVENTS_H
#define MM_EVENTS_H
#ifdef _WIN32
#pragma once
#endif
#include "mm_framework.h"
#include "utlvector.h"
class CMatchEventsSubscription : public IMatchEventsSubscription
{
// Methods of IMatchEventsSubscription
public:
virtual void Subscribe( IMatchEventsSink *pSink );
virtual void Unsubscribe( IMatchEventsSink *pSink );
virtual void BroadcastEvent( KeyValues *pEvent );
virtual void RegisterEventData( KeyValues *pEventData );
virtual KeyValues * GetEventData( char const *szEventDataKey );
public:
bool IsBroacasting() const { return m_bBroadcasting; }
void Shutdown();
public:
CMatchEventsSubscription();
~CMatchEventsSubscription();
protected:
CUtlVector< IMatchEventsSink * > m_arrSinks;
CUtlVector< int > m_arrRefCount;
CUtlVector< int > m_arrIteratorsOutstanding;
bool m_bBroadcasting;
bool m_bAllowNestedBroadcasts;
CUtlVector< KeyValues * > m_arrQueuedEvents;
CUtlVector< KeyValues * > m_arrEventData;
CUtlVector< KeyValues * > m_arrSentEvents;
};
// Match events subscription singleton
extern CMatchEventsSubscription *g_pMatchEventsSubscription;
#endif // MM_EVENTS_H

View File

@@ -0,0 +1,128 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_extensions.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CMatchExtensions::CMatchExtensions()
{
;
}
CMatchExtensions::~CMatchExtensions()
{
;
}
static CMatchExtensions g_MatchExtensions;
CMatchExtensions *g_pMatchExtensions = &g_MatchExtensions;
//
// Implementation
//
void CMatchExtensions::RegisterExtensionInterface( char const *szInterfaceString, void *pvInterface )
{
Assert( szInterfaceString );
Assert( pvInterface );
if ( !szInterfaceString || !pvInterface )
return;
RegisteredInterface_t &rInfo = m_mapRegisteredInterfaces[ szInterfaceString ];
Assert( rInfo.m_nRefCount >= 0 );
if ( rInfo.m_nRefCount > 0 )
{
// Interface already registered
Assert( pvInterface == rInfo.m_pvInterface );
}
else
{
// Fresh registration of the interface
Assert( !rInfo.m_pvInterface );
rInfo.m_pvInterface = pvInterface;
}
// Increment refcount
++ rInfo.m_nRefCount;
// Fire a callback for interface registration
OnExtensionInterfaceUpdated( szInterfaceString, pvInterface );
}
void CMatchExtensions::UnregisterExtensionInterface( char const *szInterfaceString, void *pvInterface )
{
Assert( szInterfaceString );
Assert( pvInterface );
if ( !szInterfaceString || !pvInterface )
return;
RegisteredInterface_t &rInfo = m_mapRegisteredInterfaces[ szInterfaceString ];
Assert( rInfo.m_nRefCount > 0 );
if ( rInfo.m_nRefCount <= 0 )
return;
Assert( pvInterface == rInfo.m_pvInterface );
-- rInfo.m_nRefCount;
if ( 0 == rInfo.m_nRefCount )
{
rInfo.m_pvInterface = NULL;
OnExtensionInterfaceUpdated( szInterfaceString, NULL );
}
}
void * CMatchExtensions::GetRegisteredExtensionInterface( char const *szInterfaceString )
{
RegisteredInterface_t &rInfo = m_mapRegisteredInterfaces[ szInterfaceString ];
if ( rInfo.m_nRefCount > 0 && rInfo.m_pvInterface )
return rInfo.m_pvInterface;
else
return NULL;
}
//
// Known interfaces recognition
//
void CMatchExtensions::OnExtensionInterfaceUpdated( char const *szInterfaceString, void *pvInterface )
{
typedef void * Exts_t::* Ext_t;
struct CachedInterfacePtr_t
{
char const *m_szName;
Ext_t m_ppInterface;
};
static CachedInterfacePtr_t s_table[] =
{
{ LOCALIZE_INTERFACE_VERSION, (Ext_t) &Exts_t::m_pILocalize },
{ INETSUPPORT_VERSION_STRING, (Ext_t) &Exts_t::m_pINetSupport },
{ IENGINEVOICE_INTERFACE_VERSION, (Ext_t) &Exts_t::m_pIEngineVoice },
{ VENGINE_CLIENT_INTERFACE_VERSION, (Ext_t) &Exts_t::m_pIVEngineClient },
{ INTERFACEVERSION_VENGINESERVER, (Ext_t) &Exts_t::m_pIVEngineServer },
{ INTERFACEVERSION_SERVERGAMEDLL, (Ext_t) &Exts_t::m_pIServerGameDLL },
{ INTERFACEVERSION_GAMEEVENTSMANAGER2, (Ext_t) &Exts_t::m_pIGameEventManager2 },
{ CLIENT_DLL_INTERFACE_VERSION, ( Ext_t ) &Exts_t::m_pIBaseClientDLL },
#ifdef _X360
{ XBOXSYSTEM_INTERFACE_VERSION, (Ext_t) &Exts_t::m_pIXboxSystem },
{ XONLINE_INTERFACE_VERSION, (Ext_t) &Exts_t::m_pIXOnline },
#endif
{ NULL, NULL }
};
for ( CachedInterfacePtr_t *ptr = s_table; ptr->m_szName; ++ ptr )
{
if ( !Q_stricmp( ptr->m_szName, szInterfaceString ) )
{
m_exts.*(ptr->m_ppInterface) = pvInterface;
}
}
}

View File

@@ -0,0 +1,99 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_EXTENSIONS_H
#define MM_EXTENSIONS_H
#ifdef _WIN32
#pragma once
#endif
#include "UtlStringMap.h"
#include "vgui/ILocalize.h"
#include "engine/inetsupport.h"
#include "cdll_int.h"
#include "eiface.h"
#include "igameevents.h"
#ifdef _X360
#include "ixboxsystem.h"
#endif
#include "eiface.h"
#include "mm_framework.h"
#include "engine/ienginevoice.h"
class CMatchExtensions : public IMatchExtensions
{
// Methods of IMatchExtensions
public:
// Registers an extension interface
virtual void RegisterExtensionInterface( char const *szInterfaceString, void *pvInterface );
// Unregisters an extension interface
virtual void UnregisterExtensionInterface( char const *szInterfaceString, void *pvInterface );
// Gets a pointer to a registered extension interface
virtual void * GetRegisteredExtensionInterface( char const *szInterfaceString );
public:
CMatchExtensions();
~CMatchExtensions();
protected:
struct RegisteredInterface_t
{
void *m_pvInterface;
int m_nRefCount;
RegisteredInterface_t() : m_nRefCount( 0 ), m_pvInterface( 0 ) {}
};
typedef CUtlStringMap< RegisteredInterface_t > InterfaceMap_t;
InterfaceMap_t m_mapRegisteredInterfaces;
protected:
void OnExtensionInterfaceUpdated( char const *szInterfaceString, void *pvInterface );
public:
vgui::ILocalize * GetILocalize() { return m_exts.m_pILocalize; }
INetSupport * GetINetSupport() { return m_exts.m_pINetSupport; }
IEngineVoice * GetIEngineVoice() { return m_exts.m_pIEngineVoice; }
IVEngineClient * GetIVEngineClient() { return m_exts.m_pIVEngineClient; }
IVEngineServer * GetIVEngineServer() { return m_exts.m_pIVEngineServer; }
IServerGameDLL * GetIServerGameDLL() { return m_exts.m_pIServerGameDLL; }
IGameEventManager2 * GetIGameEventManager2() { return m_exts.m_pIGameEventManager2; }
IBaseClientDLL * GetIBaseClientDLL() { return m_exts.m_pIBaseClientDLL; }
#ifdef _X360
IXboxSystem * GetIXboxSystem() { return m_exts.m_pIXboxSystem; }
IXOnline * GetIXOnline() { return m_exts.m_pIXOnline; }
#endif
protected:
// Known extension interfaces
struct Exts_t
{
inline Exts_t() { memset( this, 0, sizeof( *this ) ); }
vgui::ILocalize *m_pILocalize;
INetSupport *m_pINetSupport;
IEngineVoice *m_pIEngineVoice;
IVEngineClient *m_pIVEngineClient;
IVEngineServer *m_pIVEngineServer;
IServerGameDLL *m_pIServerGameDLL;
IGameEventManager2 *m_pIGameEventManager2;
IBaseClientDLL *m_pIBaseClientDLL;
#ifdef _X360
IXboxSystem *m_pIXboxSystem;
IXOnline *m_pIXOnline;
#endif
}
m_exts;
};
// Match title singleton
extern CMatchExtensions *g_pMatchExtensions;
#endif // MM_EXTENSIONS_H

View File

@@ -0,0 +1,960 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_framework.h"
#include "mm_netmsgcontroller.h"
#include "matchsystem.h"
#include "mm_title_main.h"
#include "x360_lobbyapi.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// Stress testing events listeners every frame
ConVar mm_events_listeners_validation( "mm_events_listeners_validation", "0", FCVAR_DEVELOPMENTONLY );
//
// Implementation of Steam invite listener
//
uint64 g_uiLastInviteFlags = 0ull;
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
class CMatchSteamInviteListener
{
public:
void RunFrame();
void Register();
STEAM_CALLBACK_MANUAL( CMatchSteamInviteListener, Steam_OnGameLobbyJoinRequested, GameLobbyJoinRequested_t, m_CallbackOnGameLobbyJoinRequested );
#ifdef _PS3
STEAM_CALLBACK_MANUAL( CMatchSteamInviteListener, Steam_OnPSNGameBootInviteResult, PSNGameBootInviteResult_t, m_CallbackOnPSNGameBootInviteResult );
#endif
protected:
GameLobbyJoinRequested_t m_msgPending;
}
g_MatchSteamInviteListener;
void CMatchSteamInviteListener::Register()
{
m_CallbackOnGameLobbyJoinRequested.Register( this, &CMatchSteamInviteListener::Steam_OnGameLobbyJoinRequested );
#ifdef _PS3
m_CallbackOnPSNGameBootInviteResult.Register( this, &CMatchSteamInviteListener::Steam_OnPSNGameBootInviteResult );
#endif
}
#else
class CMatchSteamInviteListener
{
public:
void RunFrame() {}
void Register() {}
}
g_MatchSteamInviteListener;
#endif
//
// Implementation
//
CMatchFramework::CMatchFramework() :
m_pMatchSession( NULL ),
m_bJoinTeamSession( false ),
m_pTeamSessionSettings( NULL )
{
}
CMatchFramework::~CMatchFramework()
{
;
}
InitReturnVal_t CMatchFramework::Init()
{
InitReturnVal_t ret = INIT_OK;
ret = MM_Title_Init();
if ( ret != INIT_OK )
return ret;
g_MatchSteamInviteListener.Register();
return INIT_OK;
}
void CMatchFramework::Shutdown()
{
// Shutdown event system
g_pMatchEventsSubscription->Shutdown();
// Shutdown the title
MM_Title_Shutdown();
// Cancel any pending server updates before shutdown
g_pServerManager->EnableServersUpdate( false );
// Cancel any pending datacenter queries
g_pDatacenter->EnableUpdate( false );
}
void CMatchFramework::RunFrame()
{
// Run frame listeners validation if requested
if ( mm_events_listeners_validation.GetBool() )
{
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "mm_events_listeners_validation" ) );
}
#ifdef _X360
if ( IXOnline *pIXOnline = g_pMatchExtensions->GetIXOnline() )
pIXOnline->RunFrame();
MMX360_UpdateDormantOperations();
SysSession360_UpdatePending();
#endif
RunFrame_Invite();
g_MatchSteamInviteListener.RunFrame();
#ifdef _X360
// Pump rate adjustments
MatchSession_RateAdjustmentUpdate();
#endif
// Let the network mgr run
g_pConnectionlessLanMgr->Update();
// Let the matchsystem run
g_pMatchSystem->Update();
// Let the match session run
if ( m_pMatchSession )
m_pMatchSession->Update();
// Let the match title run frame
g_pIMatchTitle->RunFrame();
if ( m_bJoinTeamSession )
{
m_bJoinTeamSession = false;
MatchSession( m_pTeamSessionSettings );
m_pTeamSessionSettings->deleteThis();
m_pTeamSessionSettings = NULL;
}
}
IMatchExtensions * CMatchFramework::GetMatchExtensions()
{
return g_pMatchExtensions;
}
IMatchEventsSubscription * CMatchFramework::GetEventsSubscription()
{
return g_pMatchEventsSubscription;
}
IMatchTitle * CMatchFramework::GetMatchTitle()
{
return g_pIMatchTitle;
}
IMatchTitleGameSettingsMgr * CMatchFramework::GetMatchTitleGameSettingsMgr()
{
return g_pIMatchTitleGameSettingsMgr;
}
IMatchNetworkMsgController * CMatchFramework::GetMatchNetworkMsgController()
{
return g_pMatchNetMsgControllerBase;
}
IMatchSystem * CMatchFramework::GetMatchSystem()
{
return g_pMatchSystem;
}
void CMatchFramework::ApplySettings( KeyValues* keyValues )
{
g_pMatchExtensions->GetIServerGameDLL()->ApplyGameSettings( keyValues );
}
#ifdef _X360
static XINVITE_INFO s_InviteInfo;
#else
static uint64 s_InviteInfo;
#endif
static bool s_bInviteSessionDelayedJoin;
static int s_nInviteConfirmed;
template < int datasize >
static bool IsZeroData( void const *pvData )
{
static char s_zerodata[ datasize ];
return !memcmp( s_zerodata, pvData, datasize );
}
static bool ValidateInviteController( int iController )
{
#ifdef _X360
XUSER_SIGNIN_STATE eSignInState = XUserGetSigninState( iController );
XUSER_SIGNIN_INFO xsi = {0};
if ( ( eSignInState != eXUserSigninState_SignedInToLive ) ||
( ERROR_SUCCESS != XUserGetSigninInfo( iController, XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) ) ||
! ( xsi.dwInfoFlags & XUSER_INFO_FLAG_LIVE_ENABLED ) )
{
DevWarning( "ValidateInviteController: ctrl%d check1 failed (state=%d, flags=0x%X, xuid=%llx)!\n",
eSignInState, xsi.dwInfoFlags, xsi.xuid );
if ( KeyValues *notify = new KeyValues(
"OnInvite", "action", "error", "error", "NotOnline" ) )
{
notify->SetInt( "user", iController );
g_pMatchEventsSubscription->BroadcastEvent( notify );
}
return false;
}
BOOL bMultiplayer = FALSE;
if ( ( ERROR_SUCCESS != XUserCheckPrivilege( iController, XPRIVILEGE_MULTIPLAYER_SESSIONS, &bMultiplayer ) ) ||
( !bMultiplayer ) )
{
DevWarning( "ValidateInviteController: ctrl%d check2 failed (state=%d, flags=0x%X, xuid=%llx) - on multiplayer priv!\n",
eSignInState, xsi.dwInfoFlags, xsi.xuid );
if ( KeyValues *notify = new KeyValues(
"OnInvite", "action", "error", "error", "NoMultiplayer" ) )
{
notify->SetInt( "user", iController );
g_pMatchEventsSubscription->BroadcastEvent( notify );
}
return false;
}
#endif
return true;
}
static bool ValidateInviteControllers()
{
for ( DWORD k = 0; k < XBX_GetNumGameUsers(); ++ k )
{
if ( !ValidateInviteController( XBX_GetUserId( k ) ) )
return false;
}
return true;
}
static bool VerifyInviteEligibility()
{
#ifdef _X360
// Make sure that the inviter is not signed in
for ( int k = 0; k < XUSER_MAX_COUNT; ++ k )
{
XUID xuid;
if ( ERROR_SUCCESS == XUserGetXUID( k, &xuid ) &&
xuid == s_InviteInfo.xuidInviter )
{
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues(
"OnInvite", "action", "error", "error", "SameConsole" ) );
return false;
}
}
// Check if the user is currently inactive
bool bExistingUser = false;
for ( DWORD k = 0; k < XBX_GetNumGameUsers(); ++ k )
{
if ( XBX_GetInvitedUserId() == (DWORD) XBX_GetUserId( k ) &&
!XBX_GetUserIsGuest( k ) )
{
bExistingUser = true;
break;
}
}
// Check if this is the existing user that the invite is for a different session
// than the session they are currently in (e.g. they are in a lobby and do
// "Join Party and Game" with another user who is in the same lobby)
char chInviteSessionInfo[ XSESSION_INFO_STRING_LENGTH ] = {0};
MMX360_SessionInfoToString( s_InviteInfo.hostInfo, chInviteSessionInfo );
if ( IMatchSession *pIMatchSession = g_pMatchFramework->GetMatchSession() )
{
bool bJoinable = ( ( IMatchSessionInternal * ) pIMatchSession )->IsAnotherSessionJoinable( chInviteSessionInfo );
if ( !bJoinable && bExistingUser )
{
Warning( "VerifyInviteEligibility: declined invite due to local session!\n" );
return false;
}
}
// New user is eligible since otherwise he shouldn't be able to accept an invite
if ( !bExistingUser || ( XBX_GetNumGameUsers() < 2 ) ||
( g_pMMF->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_INVITE_ONLY_SINGLE_USER ) )
{
if ( !ValidateInviteController( XBX_GetInvitedUserId() ) )
return false;
else
return true;
}
#endif
// Check that every user is valid
return ValidateInviteControllers();
}
static void JoinInviteSession()
{
s_bInviteSessionDelayedJoin = false;
#ifdef _X360
if ( 0ull == ( uint64 const & ) s_InviteInfo.hostInfo.sessionID )
#else
if ( !s_InviteInfo )
#endif
return;
if ( g_pMatchExtensions->GetIVEngineClient()->IsDrawingLoadingImage() )
{
s_bInviteSessionDelayedJoin = true;
return;
}
// Invites cannot be accepted from inside an event broadcast
// internally used events must be top-level events since they
// operate on signed in / active users, trigger playermanager,
// account access and other events
// Wait until next frame in such case
if ( g_pMatchEventsSubscription && g_pMatchEventsSubscription->IsBroacasting() )
{
s_bInviteSessionDelayedJoin = true;
return;
}
#if !defined( NO_STEAM ) && !defined( _GAMECONSOLE ) && !defined( SWDS )
extern bool g_bSteamStatsReceived;
if ( !g_bSteamStatsReceived && ( g_uiLastInviteFlags & MM_INVITE_FLAG_PCBOOT ) )
{
s_bInviteSessionDelayedJoin = true;
return;
}
#endif
#ifdef _X360
DevMsg( "JoinInviteSession: sessionid = %llx, xuid = %llx\n", ( uint64 const & ) s_InviteInfo.hostInfo.sessionID, s_InviteInfo.xuidInvitee );
#else
DevMsg( "JoinInviteSession: sessionid = %llx\n", s_InviteInfo );
#endif
//
// Validate the user accepting the invite
//
#ifdef _GAMECONSOLE
if ( XBX_GetInvitedUserId() == INVALID_USER_ID )
{
DevWarning( "JoinInviteSession: no invited user!\n" );
return;
}
#endif
#ifdef _X360
XUSER_SIGNIN_STATE eSignInState = XUserGetSigninState( XBX_GetInvitedUserId() );
XUSER_SIGNIN_INFO xsi = {0};
if ( ( eSignInState != eXUserSigninState_SignedInToLive ) ||
( ERROR_SUCCESS != XUserGetSigninInfo( XBX_GetInvitedUserId(), XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) ) ||
! ( xsi.dwInfoFlags & XUSER_INFO_FLAG_LIVE_ENABLED ) ||
( xsi.dwInfoFlags & XUSER_INFO_FLAG_GUEST ) ||
!IsEqualXUID( xsi.xuid, s_InviteInfo.xuidInvitee ) )
{
DevWarning( "JoinInviteSession: invited user signin information validation failed (state=%d, flags=0x%X, xuid=%llx)!\n",
eSignInState, xsi.dwInfoFlags, xsi.xuid );
return;
}
BOOL bMultiplayer = FALSE;
if ( ( ERROR_SUCCESS != XUserCheckPrivilege( XBX_GetInvitedUserId(), XPRIVILEGE_MULTIPLAYER_SESSIONS, &bMultiplayer ) ) ||
( !bMultiplayer ) )
{
DevWarning( "JoinInviteSession: no multiplayer priv!\n" );
return;
}
#endif
//
// Check if the currently-involved user is accepting the invite
//
#ifdef _GAMECONSOLE
bool bExistingUser = false;
for ( DWORD k = 0; k < XBX_GetNumGameUsers(); ++ k )
{
if ( XBX_GetInvitedUserId() == (DWORD) XBX_GetUserId( k ) &&
!XBX_GetUserIsGuest( k ) )
{
bExistingUser = true;
break;
}
}
if ( !bExistingUser ||
( ( XBX_GetNumGameUsers() > 1 ) && ( g_pMMF->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_INVITE_ONLY_SINGLE_USER ) ) )
{
// Another controller is accepting the invite or guest status
// has changed.
// then we need to reset all our XBX core state:
DevMsg( "JoinInviteSession: activating inactive controller%d\n", XBX_GetInvitedUserId() );
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfilesWriteOpportunity", "reason", "deactivation" ) );
XBX_ClearUserIdSlots();
XBX_SetPrimaryUserId( XBX_GetInvitedUserId() );
XBX_SetPrimaryUserIsGuest( 0 );
XBX_SetUserId( 0, XBX_GetInvitedUserId() );
XBX_SetUserIsGuest( 0, 0 );
XBX_SetNumGameUsers( 1 );
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfilesChanged", "numProfiles", int(1) ) );
IPlayerLocal *pPlayer = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() );
if ( !pPlayer )
{
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues(
"OnInvite", "action", "error", "error", "" ) );
return;
}
( ( PlayerLocal * ) pPlayer )->SetFlag_AwaitingTitleData();
// Since we have activated a new profile, we need to wait until title data gets loaded
DevMsg( "JoinInviteSession: activated inactive controller%d, waiting for title data...\n", XBX_GetInvitedUserId() );
return;
}
#endif
// Validate storage device
s_nInviteConfirmed = -1;
if ( KeyValues *notify = new KeyValues( "OnInvite" ) )
{
#ifdef _X360
char chSessionInfo[ XSESSION_INFO_STRING_LENGTH ] = {0};
MMX360_SessionInfoToString( s_InviteInfo.hostInfo, chSessionInfo );
notify->SetInt( "user", XBX_GetInvitedUserId() );
notify->SetString( "sessioninfo", chSessionInfo );
#else
notify->SetUint64( "sessionid", s_InviteInfo );
#endif
notify->SetString( "action", "storage" );
notify->SetPtr( "confirmed", &s_nInviteConfirmed );
g_pMatchEventsSubscription->BroadcastEvent( notify );
// If handlers decided they need to confirm storage devices, etc.
if ( s_nInviteConfirmed != -1 )
{
DevMsg( "JoinInviteSession: waiting for storage device selection...\n" );
return;
}
}
// Verify eligibility
DevMsg( "JoinInviteSession: verifying eligibility...\n" );
if ( !VerifyInviteEligibility() )
return;
DevMsg( "JoinInviteSession: connecting...\n" );
//
// Argument validation
//
#ifdef _GAMECONSOLE
Assert( XBX_GetInvitedUserId() >= 0 );
Assert( XBX_GetInvitedUserId() < XUSER_MAX_COUNT );
Assert( XBX_GetSlotByUserId( XBX_GetInvitedUserId() ) < ( int ) XBX_GetNumGameUsers() );
Assert( XBX_GetNumGameUsers() < MAX_SPLITSCREEN_CLIENTS );
#endif
// Requesting to join the stored off session
KeyValues *pSettings = KeyValues::FromString(
"settings",
" system { "
" network LIVE "
" } "
" options { "
" action joinsession "
" } "
);
#ifdef _X360
pSettings->SetUint64( "options/sessionid", ( const uint64 & ) s_InviteInfo.hostInfo.sessionID );
if ( !IsZeroData< sizeof( s_InviteInfo.hostInfo.keyExchangeKey ) >( &s_InviteInfo.hostInfo.keyExchangeKey ) )
{
// Missing sessioninfo will cause the session info to be discovered during session
// creation time
char chSessionInfoBuffer[ XSESSION_INFO_STRING_LENGTH ] = {0};
MMX360_SessionInfoToString( s_InviteInfo.hostInfo, chSessionInfoBuffer );
pSettings->SetString( "options/sessioninfo", chSessionInfoBuffer );
}
#else
pSettings->SetUint64( "options/sessionid", s_InviteInfo );
#endif
KeyValues::AutoDelete autodelete( pSettings );
Q_memset( &s_InviteInfo, 0, sizeof( s_InviteInfo ) );
g_pMatchFramework->MatchSession( pSettings );
}
static void OnInviteAccepted()
{
// Verify eligibility
DevMsg( "OnInviteAccepted: verifying eligibility...\n" );
if ( !VerifyInviteEligibility() )
return;
DevMsg( "OnInviteAccepted: confirming...\n" );
// Make sure the user confirms the invite
s_nInviteConfirmed = -1;
if ( KeyValues *notify = new KeyValues( "OnInvite" ) )
{
#ifdef _X360
char chSessionInfo[ XSESSION_INFO_STRING_LENGTH ] = {0};
MMX360_SessionInfoToString( s_InviteInfo.hostInfo, chSessionInfo );
notify->SetInt( "user", XBX_GetInvitedUserId() );
notify->SetString( "sessioninfo", chSessionInfo );
#else
notify->SetUint64( "sessionid", s_InviteInfo );
#endif
notify->SetString( "action", "accepted" );
notify->SetPtr( "confirmed", &s_nInviteConfirmed );
g_pMatchEventsSubscription->BroadcastEvent( notify );
// If handlers decided they need to confirm destructive actions or
// select storage devices, etc.
if ( s_nInviteConfirmed != -1 )
{
DevMsg( "OnInviteAccepted: waiting for confirmation...\n" );
return;
}
}
DevMsg( "OnInviteAccepted: accepting...\n" );
// Otherwise, launch depending on our current MOD
// if ( !Q_stricmp( GetCurrentMod(), "left4dead2" ) ) <-- for multi-game package
{
// Kick off our join
JoinInviteSession();
}
// else <-- for multi-game package supporting cross-game invites
// {
// // Save off our session ID for later retrieval
// // NOTE: We may need to actually save off the inviter's XID and search for them later on if we took too long or the
// // session they were a part of went away
//
// XBX_SetInviteSessionId( inviteInfo.hostInfo.sessionID );
//
// // Quit via the menu path "QuitNoConfirm"
// EngineVGui()->SystemNotification( SYSTEMNOTIFY_INVITE_SHUTDOWN, NULL );
// }
}
void CMatchFramework::RunFrame_Invite()
{
if ( s_bInviteSessionDelayedJoin )
JoinInviteSession();
}
void CMatchFramework::AcceptInvite( int iController )
{
#ifdef _X360
s_bInviteSessionDelayedJoin = false;
// Grab our invite info
DWORD dwError = g_pMatchExtensions->GetIXOnline()->XInviteGetAcceptedInfo( iController, &s_InviteInfo );
if ( dwError != ERROR_SUCCESS )
{
ZeroMemory( &s_InviteInfo, sizeof( s_InviteInfo ) );
return;
}
// We only care if we're asked to join this title's session
if ( s_InviteInfo.dwTitleID != GetMatchTitle()->GetTitleID() )
{
ZeroMemory( &s_InviteInfo, sizeof( s_InviteInfo ) );
return;
}
// We just mark the invited user and let the matchmaking handle profile changes
XBX_SetInvitedUserId( iController );
// Invite accepted logic after globals have been setup
OnInviteAccepted();
#endif
}
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
void CMatchSteamInviteListener::Steam_OnGameLobbyJoinRequested( GameLobbyJoinRequested_t *pJoinInvite )
{
#ifdef _PS3
if ( pJoinInvite->m_steamIDFriend.ConvertToUint64() != ~0ull )
{
g_uiLastInviteFlags = ( pJoinInvite->m_steamIDFriend.BConsoleUserAccount() ? MM_INVITE_FLAG_CONSOLE : 0 );
}
#endif
#if !defined( _GAMECONSOLE )
g_uiLastInviteFlags = ( pJoinInvite->m_steamIDFriend.ConvertToUint64() == ~0ull ) ? MM_INVITE_FLAG_PCBOOT : 0;
#endif
m_msgPending = GameLobbyJoinRequested_t();
s_bInviteSessionDelayedJoin = false;
s_InviteInfo = pJoinInvite->m_steamIDLobby.ConvertToUint64();
if ( !s_InviteInfo )
return;
#ifdef _GAMECONSOLE
// We just mark the invited user and let the matchmaking handle profile changes
XBX_SetInvitedUserId( XBX_GetPrimaryUserId() );
#endif
// Whether we have to make invite go pending
char chBuffer[2] = {};
if ( g_pMatchExtensions->GetIVEngineClient()->IsDrawingLoadingImage() ||
( g_pMatchEventsSubscription && g_pMatchEventsSubscription->IsBroacasting() ) ||
( g_pMatchExtensions->GetIBaseClientDLL()->GetStatus( chBuffer, 2 ), ( chBuffer[0] != '+' ) ) )
{
m_msgPending = *pJoinInvite;
return;
}
// Invite accepted logic after globals have been setup
OnInviteAccepted();
}
#ifdef _PS3
void CMatchSteamInviteListener::Steam_OnPSNGameBootInviteResult( PSNGameBootInviteResult_t *pParam )
{
if ( pParam->m_bGameBootInviteExists && pParam->m_steamIDLobby.IsValid() )
{
g_uiLastInviteFlags = MM_INVITE_FLAG_CONSOLE;
}
}
#endif
void CMatchSteamInviteListener::RunFrame()
{
if ( m_msgPending.m_steamIDLobby.IsValid() )
{
GameLobbyJoinRequested_t msgRequest = m_msgPending;
Steam_OnGameLobbyJoinRequested( &msgRequest );
}
}
#endif
IMatchSession *CMatchFramework::GetMatchSession()
{
return m_pMatchSession;
}
void CMatchFramework::CreateSession( KeyValues *pSettings )
{
DevMsg( "CreateSession: \n");
KeyValuesDumpAsDevMsg( pSettings );
#ifndef SWDS
if ( !pSettings )
return;
IMatchSessionInternal *pMatchSessionNew = NULL;
//
// Analyze the type of session requested to create
//
char const *szNetwork = pSettings->GetString( "system/network", "offline" );
#ifdef _X360
if ( !Q_stricmp( "LIVE", szNetwork ) && !ValidateInviteControllers() )
return;
#endif
// Recompute XUIDs for the session type that we are creating
g_pPlayerManager->RecomputePlayerXUIDs( szNetwork );
//
// Process create session request
//
if ( !Q_stricmp( "offline", szNetwork ) )
{
CMatchSessionOfflineCustom *pSession = new CMatchSessionOfflineCustom( pSettings );
pMatchSessionNew = pSession;
}
else
{
CMatchSessionOnlineHost *pSession = new CMatchSessionOnlineHost( pSettings );
pMatchSessionNew = pSession;
}
if ( pMatchSessionNew )
{
CloseSession();
m_pMatchSession = pMatchSessionNew;
}
#endif
}
void CMatchFramework::MatchSession( KeyValues *pSettings )
{
#ifndef SWDS
if ( !pSettings )
return;
DevMsg( "MatchSession: \n");
KeyValuesDumpAsDevMsg( pSettings );
IMatchSessionInternal *pMatchSessionNew = NULL;
//
// Analyze what kind of client-side matchmaking
// needs to happen.
//
char const *szNetwork = pSettings->GetString( "system/network", "LIVE" );
char const *szAction = pSettings->GetString( "options/action", "" );
// Recompute XUIDs for the session type that we are creating
g_pPlayerManager->RecomputePlayerXUIDs( szNetwork );
//
// Process match session request
//
if ( !Q_stricmp( "joinsession", szAction ) )
{
#ifdef _X360
// For LIVE sessions we need to be eligible
if ( !Q_stricmp( "LIVE", szNetwork ) && !ValidateInviteControllers() )
return;
#endif
// We have an explicit session to join
CMatchSessionOnlineClient *pSession = new CMatchSessionOnlineClient( pSettings );
pMatchSessionNew = pSession;
}
else if ( !Q_stricmp( "joininvitesession", szAction ) )
{
#ifdef _X360
ZeroMemory( &s_InviteInfo, sizeof( s_InviteInfo ) );
XUSER_SIGNIN_INFO xsi;
if ( ERROR_SUCCESS == XUserGetSigninInfo( XBX_GetInvitedUserId(), XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) )
s_InviteInfo.xuidInvitee = xsi.xuid;
uint64 uiSessionID = pSettings->GetUint64( "options/sessionid", 0ull );
s_InviteInfo.hostInfo.sessionID = ( XNKID & ) uiSessionID;
OnInviteAccepted();
#endif
}
else // "quickmatch" or "custommatch"
{
#ifdef _X360
// For LIVE sessions we need to be eligible
if ( !Q_stricmp( "LIVE", szNetwork ) && !ValidateInviteControllers() )
return;
#endif
CMatchSessionOnlineSearch *pSession = new CMatchSessionOnlineSearch( pSettings );
pMatchSessionNew = pSession;
}
if ( pMatchSessionNew )
{
CloseSession();
m_pMatchSession = pMatchSessionNew;
}
#endif
}
void CMatchFramework::CloseSession()
{
// Destroy the session
if ( m_pMatchSession )
{
IMatchSessionInternal *pMatchSession = m_pMatchSession;
m_pMatchSession = NULL;
pMatchSession->Destroy();
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "closed" ) );
}
}
bool CMatchFramework::IsOnlineGame( void )
{
IMatchSession *pMatchSession = GetMatchSession();
if ( pMatchSession )
{
KeyValues* kv = pMatchSession->GetSessionSettings();
if ( kv )
{
char const *szMode = kv->GetString( "system/network", NULL );
if ( szMode && !V_stricmp( "LIVE", szMode ) )
{
return true;
}
}
}
return false;
}
void CMatchFramework::UpdateTeamProperties( KeyValues *pTeamProperties )
{
IMatchSession *pMatchSession = GetMatchSession();
IMatchTitleGameSettingsMgr *pMatchTitleGameSettingsMgr = GetMatchTitleGameSettingsMgr();
if ( pMatchSession && pMatchTitleGameSettingsMgr )
{
pMatchSession->UpdateTeamProperties( pTeamProperties );
KeyValues *pCurrentSettings = pMatchSession->GetSessionSettings();
pMatchTitleGameSettingsMgr->UpdateTeamProperties( pCurrentSettings, pTeamProperties );
}
}
void CMatchFramework::OnEvent( KeyValues *pEvent )
{
char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "mmF->CloseSession", szEvent ) )
{
CloseSession();
return;
}
else if ( !Q_stricmp( "OnInvite", szEvent ) )
{
if ( !Q_stricmp( "join", pEvent->GetString( "action" ) ) )
{
s_bInviteSessionDelayedJoin = true;
}
else if ( !Q_stricmp( "deny", pEvent->GetString( "action" ) ) )
{
Q_memset( &s_InviteInfo, 0, sizeof( s_InviteInfo ) );
s_bInviteSessionDelayedJoin = false;
}
return;
}
else if ( !Q_stricmp( "OnSteamOverlayCall::LobbyJoin", szEvent ) )
{
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
GameLobbyJoinRequested_t msg;
msg.m_steamIDLobby.SetFromUint64( pEvent->GetUint64( "sessionid" ) );
msg.m_steamIDFriend.SetFromUint64( ~0ull );
g_MatchSteamInviteListener.Steam_OnGameLobbyJoinRequested( &msg );
#endif
return;
}
else if ( !Q_stricmp( "OnMatchSessionUpdate", szEvent ) )
{
KeyValues *pUpdate = pEvent->FindKey( "update" );
if ( pUpdate )
{
const char *pAction = pUpdate->GetString( "options/action", "" );
if ( !Q_stricmp( "joinsession", pAction ) )
{
KeyValues *pTeamMembers = pUpdate->FindKey( "teamMembers" );
if ( pTeamMembers )
{
// Received console team match settings from host
// Find what team we are on
int numPlayers = pTeamMembers->GetInt( "numPlayers" );
int playerTeam = -1;
int activeUer = XBX_GetPrimaryUserId();
IPlayerLocal *player = g_pPlayerManager->GetLocalPlayer( activeUer );
uint64 localPlayerId = player->GetXUID();
for ( int i = 0; i < numPlayers; i++ )
{
KeyValues *pTeamPlayer = pTeamMembers->FindKey( CFmtStr( "player%d", i ) );
uint64 playerId = pTeamPlayer->GetUint64( "xuid" );
if ( playerId == localPlayerId )
{
int team = pTeamPlayer->GetInt( "team" );
DevMsg( "Adding player %llu to team %d\n", playerId, team );
playerTeam = team;
break;
}
}
m_pTeamSessionSettings = pUpdate->MakeCopy();
m_pTeamSessionSettings->SetName( "settings ");
// Delete the "teamMembers" key
m_pTeamSessionSettings->RemoveSubKey( m_pTeamSessionSettings->FindKey( "teamMembers" ) );
// Add "conteam" value
m_pTeamSessionSettings->SetInt( "conteam", playerTeam );
// Add the "sessionHostDataUnpacked" key
KeyValues *pSessionHostDataSrc = pUpdate->FindKey( "sessionHostDataUnpacked" );
if ( pSessionHostDataSrc )
{
KeyValues *pSessionHostDataDst = m_pTeamSessionSettings->CreateNewKey();
pSessionHostDataDst->SetName( "sessionHostDataUnpacked" );
pSessionHostDataSrc->CopySubkeys( pSessionHostDataDst );
}
m_bJoinTeamSession = true;
}
}
}
}
//
// Delegate to the managers
//
if ( g_pPlayerManager )
g_pPlayerManager->OnEvent( pEvent );
if ( g_pServerManager )
g_pServerManager->OnEvent( pEvent );
if ( g_pDatacenter )
g_pDatacenter->OnEvent( pEvent );
if ( g_pDlcManager )
g_pDlcManager->OnEvent( pEvent );
//
// Delegate to the title
//
if ( g_pIMatchTitleEventsSink )
g_pIMatchTitleEventsSink->OnEvent( pEvent );
//
// Delegate to the session
//
if ( m_pMatchSession )
m_pMatchSession->OnEvent( pEvent );
}
void CMatchFramework::SetCurrentMatchSession( IMatchSessionInternal *pNewMatchSession )
{
m_pMatchSession = pNewMatchSession;
}
uint64 CMatchFramework::GetLastInviteFlags()
{
return g_uiLastInviteFlags;
}

155
matchmaking/mm_framework.h Normal file
View File

@@ -0,0 +1,155 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_FRAMEWORK_H
#define MM_FRAMEWORK_H
#ifdef _WIN32
#pragma once
#endif
#include "tier0/dbg.h"
#include "tier0/icommandline.h"
#include "tier1/strtools.h"
#include "tier1/checksum_crc.h"
#include "tier1/keyvalues.h"
#include "tier1/utlbuffer.h"
#include "tier1/fmtstr.h"
#include "mathlib/mathlib.h"
#include "const.h"
#include "inetmsghandler.h"
#include "appframework/IAppSystemGroup.h"
#include "matchmaking/imatchframework.h"
#include "igameevents.h"
#include "tier2/tier2.h"
#include "vstdlib/jobthread.h"
#include "extkeyvalues.h"
#include "steam_apihook.h"
class CMatchFramework;
#include "mm_extensions.h"
#include "mm_events.h"
#include "mm_voice.h"
#include "mm_session.h"
#include "mm_netmgr.h"
#include "matchsystem.h"
#include "playermanager.h"
#include "servermanager.h"
#include "searchmanager.h"
#include "datacenter.h"
#include "mm_dlc.h"
enum MatchFrameworkInviteFlags_t
{
// Indicates that invite was received using console mechanisms (XMB/boot/etc.)
MM_INVITE_FLAG_CONSOLE = ( 1 << 0 ),
// Indicates that the game was booted through a Steam invite
MM_INVITE_FLAG_PCBOOT = ( 1 << 1 ),
};
class CMatchFramework :
public CTier2AppSystem< IMatchFramework >,
public IMatchEventsSink
{
// Methods of IAppSystem
public:
virtual bool Connect( CreateInterfaceFn factory );
virtual void Disconnect();
virtual void *QueryInterface( const char *pInterfaceName );
virtual InitReturnVal_t Init();
virtual void Shutdown();
// Methods of IMatchFramework
public:
// Run frame of the matchmaking framework
virtual void RunFrame();
// Get matchmaking extensions
virtual IMatchExtensions * GetMatchExtensions();
// Get events container
virtual IMatchEventsSubscription * GetEventsSubscription();
// Get the matchmaking title interface
virtual IMatchTitle * GetMatchTitle();
// Get the match session interface of the current match framework type
virtual IMatchSession * GetMatchSession();
// Get the network msg encode/decode factory
virtual IMatchNetworkMsgController * GetMatchNetworkMsgController();
// Get the match system
virtual IMatchSystem * GetMatchSystem();
// Send the key values back to the server
virtual void ApplySettings( KeyValues* keyValues );
// Entry point to create session
virtual void CreateSession( KeyValues *pSettings );
// Entry point to match into a session
virtual void MatchSession( KeyValues *pSettings );
// Accept invite
virtual void AcceptInvite( int iController );
// Close the session
virtual void CloseSession();
// Checks to see if the current game is being played online ( as opposed to locally against bots )
virtual bool IsOnlineGame( void );
// Called by the client to notify matchmaking that it should update matchmaking properties based
// on player distribution among the teams.
virtual void UpdateTeamProperties( KeyValues *pTeamProperties );
//
// IMatchEventsSink
//
public:
virtual void OnEvent( KeyValues *pEvent );
// Additional matchmaking title-defined interface
public:
virtual IMatchTitleGameSettingsMgr * GetMatchTitleGameSettingsMgr();
public:
void SetCurrentMatchSession( IMatchSessionInternal *pNewMatchSession );
uint64 GetLastInviteFlags();
protected:
void RunFrame_Invite();
public:
CMatchFramework();
~CMatchFramework();
protected:
IMatchSessionInternal *m_pMatchSession;
bool m_bJoinTeamSession;
KeyValues *m_pTeamSessionSettings;
};
extern CMatchFramework *g_pMMF;
extern const char *COM_GetModDirectory();
extern bool IsLocalClientConnectedToServer();
#endif // MM_FRAMEWORK_H

189
matchmaking/mm_netmgr.cpp Normal file
View File

@@ -0,0 +1,189 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_netmgr.h"
#include "proto_oob.h"
#include "protocol.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//
// Convars
//
ConVar net_allow_multicast( "net_allow_multicast", "1", FCVAR_RELEASE | FCVAR_ARCHIVE );
ConVar net_allow_syslink( "net_allow_syslink", "1", FCVAR_DEVELOPMENTONLY );
//
// CConnectionlessLanMgr
//
CConnectionlessLanMgr::CConnectionlessLanMgr()
{
;
}
CConnectionlessLanMgr::~CConnectionlessLanMgr()
{
;
}
static CConnectionlessLanMgr g_ConnectionlessLanMgr;
CConnectionlessLanMgr *g_pConnectionlessLanMgr = &g_ConnectionlessLanMgr;
//
// Implementation of CConnectionlessLanMgr
//
KeyValues * CConnectionlessLanMgr::UnpackPacket( netpacket_t *packet )
{
if ( !packet )
return NULL;
if ( !packet->size || !packet->data )
return NULL;
// Try to unpack the data
if ( packet->message.ReadLong() != 0 )
return NULL;
if ( packet->message.ReadLong() != g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() )
return NULL;
MEM_ALLOC_CREDIT();
int nDataLen = packet->message.ReadLong();
m_buffer.Clear();
m_buffer.EnsureCapacity( nDataLen );
packet->message.ReadBytes( m_buffer.Base(), nDataLen );
m_buffer.SeekPut( CUtlBuffer::SEEK_HEAD, nDataLen );
m_buffer.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
// Unpack key values
KeyValues *pMsg = new KeyValues( "" );
if ( pMsg->ReadAsBinary( m_buffer ) )
{
// Read binary data as well
if ( int nBinarySize = packet->message.ReadLong() )
{
m_buffer.Clear();
m_buffer.EnsureCapacity( nBinarySize );
packet->message.ReadBytes( m_buffer.Base(), nBinarySize );
m_buffer.SeekPut( CUtlBuffer::SEEK_HEAD, nBinarySize );
// set the ptr to point to our buffer
pMsg->SetPtr( "binary/ptr", m_buffer.Base() );
}
return pMsg; // "pMsg" is deleted by caller
}
pMsg->deleteThis();
return NULL;
}
bool CConnectionlessLanMgr::ProcessConnectionlessPacket( netpacket_t *packet )
{
// Unpack key values
KeyValues *pMsg = UnpackPacket( packet );
if ( !pMsg )
return false;
MEM_ALLOC_CREDIT();
KeyValues *notify = new KeyValues( "OnNetLanConnectionlessPacket" );
notify->SetString( "from", ns_address_render( packet->from ).String() );
notify->AddSubKey( pMsg );
g_pMatchEventsSubscription->BroadcastEvent( notify );
return true; // "pMsg" is deleted as child of "notify"
}
void CConnectionlessLanMgr::Update()
{
#ifdef _X360
if ( !net_allow_syslink.GetBool() )
return;
g_pMatchExtensions->GetINetSupport()->ProcessSocket( INetSupport::NS_SOCK_SYSTEMLINK, this );
#endif
}
void CConnectionlessLanMgr::SendPacket( KeyValues *pMsg, char const *szAddress /*= NULL*/, INetSupport::NetworkSocket_t eSock )
{
char buf[ INetSupport::NC_MAX_ROUTABLE_PAYLOAD ];
bf_write msg( buf, sizeof( buf ) );
msg.WriteLong( CONNECTIONLESS_HEADER );
msg.WriteLong( 0 );
msg.WriteLong( g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() );
CUtlBuffer data;
data.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
pMsg->WriteAsBinary( data );
msg.WriteLong( data.TellMaxPut() );
msg.WriteBytes( data.Base(), data.TellMaxPut() );
Assert( !msg.IsOverflowed() );
// Special case when encoding binary data
KeyValues *kvPtr = pMsg->FindKey( "binary/ptr" );
KeyValues *kvSize = pMsg->FindKey( "binary/size" );
if ( kvPtr && kvSize )
{
void *pvData = kvPtr->GetPtr();
int nSize = kvSize->GetInt();
if ( pvData && nSize )
{
msg.WriteLong( nSize );
if ( !msg.WriteBytes( pvData, nSize ) )
{
Assert( 0 );
return;
}
}
else
{
msg.WriteLong( 0 );
}
}
else
{
msg.WriteLong( 0 );
}
Assert( !msg.IsOverflowed() );
// Prepare the address
netadr_t inetAddr;
if ( szAddress )
{
if ( szAddress[0] == '*' && szAddress[1] == ':' )
{
inetAddr.SetType( NA_BROADCAST );
inetAddr.SetPort( atoi( szAddress + 2 ) );
}
else
{
inetAddr.SetFromString( szAddress );
}
}
else
{
inetAddr.SetType( NA_BROADCAST );
inetAddr.SetPort( 0 );
}
// Check if broadcasts are allowed
if ( inetAddr.GetType() == NA_BROADCAST &&
!net_allow_multicast.GetBool() )
return;
// Sending the connectionless packet
g_pMatchExtensions->GetINetSupport()->SendPacket( NULL, eSock,
inetAddr, msg.GetData(), msg.GetNumBytesWritten() );
}

47
matchmaking/mm_netmgr.h Normal file
View File

@@ -0,0 +1,47 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_NETMGR_H
#define MM_NETMGR_H
#ifdef _WIN32
#pragma once
#endif
class CConnectionlessLanMgr;
#include "mm_framework.h"
class CConnectionlessLanMgr : public IConnectionlessPacketHandler
{
//
// IConnectionlessPacketHandler
//
public:
virtual bool ProcessConnectionlessPacket( netpacket_t *packet );
public:
void Update();
void SendPacket( KeyValues *msg, char const *szAddress = NULL, INetSupport::NetworkSocket_t eSock
#ifdef _X360
= INetSupport::NS_SOCK_SYSTEMLINK
#else
= INetSupport::NS_SOCK_CLIENT
#endif
);
KeyValues * UnpackPacket( netpacket_t *packet );
public:
CConnectionlessLanMgr();
~CConnectionlessLanMgr();
protected:
CUtlBuffer m_buffer;
};
// Match events subscription singleton
extern CConnectionlessLanMgr *g_pConnectionlessLanMgr;
#endif // MM_EVENTS_H

View File

@@ -0,0 +1,306 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_netmsgcontroller.h"
#include "matchmakingqos.h"
#include "proto_oob.h"
#include "bitbuf.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//
// Implementation
//
CMatchNetworkMsgControllerBase::CMatchNetworkMsgControllerBase()
{
}
CMatchNetworkMsgControllerBase::~CMatchNetworkMsgControllerBase()
{
}
static CMatchNetworkMsgControllerBase g_MatchNetMsgControllerBase;
CMatchNetworkMsgControllerBase *g_pMatchNetMsgControllerBase = &g_MatchNetMsgControllerBase;
//
// A way to copy just value
//
static void CopyValue( KeyValues *pTo, KeyValues *pFrom )
{
switch ( pFrom->GetDataType() )
{
case KeyValues::TYPE_INT:
pTo->SetInt( "", pFrom->GetInt() );
break;
case KeyValues::TYPE_UINT64:
pTo->SetUint64( "", pFrom->GetUint64() );
break;
case KeyValues::TYPE_STRING:
pTo->SetString( "", pFrom->GetString() );
break;
default:
DevWarning( "NetMsgCtrlr::CopyValue using unknown type!\n" );
Assert( 0 );
break;
}
}
//
// Implementation
//
MM_QOS_t CMatchNetworkMsgControllerBase::GetQOS()
{
return MM_GetQos();
}
KeyValues * CMatchNetworkMsgControllerBase::GetActiveServerGameDetails( KeyValues *pRequest )
{
// Query server info
INetSupport::ServerInfo_t si;
g_pMatchExtensions->GetINetSupport()->GetServerInfo( &si );
KeyValues *pDetails = NULL;
if ( si.m_bActive )
{
MEM_ALLOC_CREDIT();
//
// Parse the game details from the values
//
pDetails = KeyValues::FromString(
"GameDetailsServer",
" system { "
" network LIVE "
" access public "
" } "
" server { "
" name = "
" server = "
" adronline = "
" adrlocal = "
" } "
" members { "
" numSlots #int#0 "
" numPlayers #int#0 "
" } "
);
//
// For a listen server and other MM session overlay the session settings
//
if ( !si.m_bDedicated && g_pMatchFramework->GetMatchSession() )
{
pDetails->MergeFrom( g_pMatchFramework->GetMatchSession()->GetSessionSettings(), KeyValues::MERGE_KV_BORROW );
}
//
// Get server information
//
pDetails->SetString( "server/name", si.m_szServerName );
pDetails->SetString( "server/server", si.m_bDedicated ? "dedicated" : "listen" );
pDetails->SetString( "server/adronline", si.m_netAdrOnline.ToString() );
pDetails->SetString( "server/adrlocal", si.m_netAdr.ToString() );
if ( si.m_bDedicated && si.m_bLobbyExclusive && si.m_bGroupExclusive )
pDetails->SetString( "system/access", "friends" );
si.m_numMaxHumanPlayers = ClampArrayBounds( si.m_numMaxHumanPlayers, g_pMMF->GetMatchTitle()->GetTotalNumPlayersSupported() );
pDetails->SetInt( "members/numSlots", si.m_numMaxHumanPlayers );
si.m_numHumanPlayers = ClampArrayBounds( si.m_numHumanPlayers, si.m_numMaxHumanPlayers );
pDetails->SetInt( "members/numPlayers", si.m_numHumanPlayers );
static ConVarRef host_info_show( "host_info_show" );
if ( host_info_show.GetInt() < 2 )
pDetails->SetString( "options/action", "crypt" );
}
else if ( IVEngineClient *pIVEngineClient = g_pMatchExtensions->GetIVEngineClient() )
{
if ( pIVEngineClient->IsLevelMainMenuBackground() )
return NULL;
char const *szLevelName = pIVEngineClient->GetLevelNameShort();
if ( !szLevelName || !*szLevelName )
return NULL;
MEM_ALLOC_CREDIT();
pDetails = new KeyValues( "GameDetailsClient" );
}
if ( !pDetails )
return NULL;
// Allow title to add game-specific settings
g_pMMF->GetMatchTitleGameSettingsMgr()->ExtendServerDetails( pDetails, pRequest );
return pDetails;
}
static KeyValues * GetLobbyDetailsTemplate( char const *szReason = "", KeyValues *pSettings = NULL )
{
KeyValues *pDetails = KeyValues::FromString(
"settings",
" system { "
" network #empty# "
" access #empty# "
" netflag #empty# "
" lock #empty# "
" } "
" options { "
" server #empty# "
" } "
" members { "
" numSlots #int#0 "
" numPlayers #int#0 "
" } "
);
g_pMMF->GetMatchTitleGameSettingsMgr()->ExtendLobbyDetailsTemplate( pDetails, szReason, pSettings );
return pDetails;
}
KeyValues * CMatchNetworkMsgControllerBase::UnpackGameDetailsFromQOS( MM_GameDetails_QOS_t const *pvQosReply )
{
//
// Check if we have correct header
//
CUtlBuffer bufQos( pvQosReply->m_pvData, pvQosReply->m_numDataBytes, CUtlBuffer::READ_ONLY );
bufQos.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
int iProtocol = bufQos.GetInt();
int iVersion = bufQos.GetInt();
if ( iProtocol != g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() )
return NULL;
if ( 0 != iVersion )
return NULL;
//
// Read the game details that we have received
//
MEM_ALLOC_CREDIT();
KeyValues *pDetails = new KeyValues( "" );
if ( !pDetails->ReadAsBinary( bufQos ) )
{
pDetails->deleteThis();
return NULL;
}
// Read the terminator
int iTerm = bufQos.GetInt();
if ( iTerm != 0 )
{
DevWarning( "UnpackGameDetailsFromQOS found bad QOS block terminator!\n" );
}
return pDetails;
}
void CMatchNetworkMsgControllerBase::PackageGameDetailsForQOS( KeyValues *pSettings, CUtlBuffer &buf )
{
KeyValues *pDetails = GetLobbyDetailsTemplate( "qos", pSettings );
KeyValues::AutoDelete autodelete( pDetails );
// Keep only keys specified in the template
pDetails->MergeFrom( pSettings, KeyValues::MERGE_KV_BORROW );
// Write the details as binary
buf.PutInt( g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() );
buf.PutInt( 0 );
pDetails->WriteAsBinary( buf );
buf.PutInt( 0 );
}
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
static void UnpackGameDetailsFromSteamLobbyInKey( uint64 uiLobbyID, char const *szPath, KeyValues *pKey )
{
// Iterate over all the values
for ( KeyValues *val = pKey->GetFirstValue(); val; val = val->GetNextValue() )
{
char const *szLobbyData = steamapicontext->SteamMatchmaking()
->GetLobbyData( uiLobbyID, CFmtStr( "%s%s", szPath, val->GetName() ) );
switch ( val->GetDataType() )
{
case KeyValues::TYPE_INT:
val->SetInt( "", atoi( szLobbyData ) );
break;
case KeyValues::TYPE_STRING:
val->SetString( "", szLobbyData );
break;
default:
DevWarning( "UnpackGameDetailsFromSteamLobby defined unknown type in schema!\n" );
Assert( 0 );
break;
}
}
// Iterate over subkeys
for ( KeyValues *sub = pKey->GetFirstTrueSubKey(); sub; sub = sub->GetNextTrueSubKey() )
{
UnpackGameDetailsFromSteamLobbyInKey( uiLobbyID, CFmtStr( "%s%s:", szPath, sub->GetName() ), sub );
}
}
#endif
KeyValues * CMatchNetworkMsgControllerBase::UnpackGameDetailsFromSteamLobby( uint64 uiLobbyID )
{
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
// Make sure the basic metadata is set on the lobby
char const *arrRequiredMetadata[] = { "system:network", "system:access" };
for ( int k = 0; k < ARRAYSIZE( arrRequiredMetadata ); ++ k )
{
char const *szMetadata = steamapicontext->SteamMatchmaking()->GetLobbyData( uiLobbyID, arrRequiredMetadata[k] );
if ( !szMetadata || !*szMetadata )
return NULL;
}
// Allocate details template
KeyValues *pDetails = GetLobbyDetailsTemplate();
// Iterate over all the keys
UnpackGameDetailsFromSteamLobbyInKey( uiLobbyID, "", pDetails );
// Get members info
if ( KeyValues *kvMembers = pDetails->FindKey( "members", true ) )
{
int numSlots = steamapicontext->SteamMatchmaking()->GetLobbyMemberLimit( uiLobbyID );
numSlots = ClampArrayBounds( numSlots, g_pMMF->GetMatchTitle()->GetTotalNumPlayersSupported() );
kvMembers->SetInt( "numSlots", numSlots );
int numPlayers = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( uiLobbyID );
numPlayers = ClampArrayBounds( numPlayers, numSlots );
kvMembers->SetInt( "numPlayers", numPlayers );
}
return pDetails;
#endif
return NULL;
}
KeyValues * CMatchNetworkMsgControllerBase::PackageGameDetailsForReservation( KeyValues *pSettings )
{
KeyValues *res = GetLobbyDetailsTemplate( "reserve", pSettings );
res->SetName( COM_GetModDirectory() );
// Keep only keys specified in the template
res->MergeFrom( pSettings, KeyValues::MERGE_KV_BORROW );
return res;
}

View File

@@ -0,0 +1,39 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_NETMSGCONTROLLER_H
#define MM_NETMSGCONTROLLER_H
#ifdef _WIN32
#pragma once
#endif
#include "mm_framework.h"
class CMatchNetworkMsgControllerBase : public IMatchNetworkMsgController
{
// Methods of IMatchNetworkMsgController
public:
// To determine host Quality-of-Service
virtual MM_QOS_t GetQOS();
virtual KeyValues * GetActiveServerGameDetails( KeyValues *pRequest );
virtual KeyValues * UnpackGameDetailsFromQOS( MM_GameDetails_QOS_t const *pvQosReply );
virtual KeyValues * UnpackGameDetailsFromSteamLobby( uint64 uiLobbyID );
virtual void PackageGameDetailsForQOS( KeyValues *pSettings, CUtlBuffer &buf );
virtual KeyValues * PackageGameDetailsForReservation( KeyValues *pSettings );
public:
CMatchNetworkMsgControllerBase();
~CMatchNetworkMsgControllerBase();
};
// Match title singleton
extern CMatchNetworkMsgControllerBase *g_pMatchNetMsgControllerBase;
#endif // MM_NETMSGCONTROLLER_H

468
matchmaking/mm_session.cpp Normal file
View File

@@ -0,0 +1,468 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_framework.h"
#include "filesystem.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifdef _X360
//-----------------------------------------------------------------------------
// Purpose: Adjust our rate based on our quality of service
//-----------------------------------------------------------------------------
static ConVar mm_clientrateupdate_enabled( "mm_clientrateupdate_enabled", "1", 0, "Automatically update the client rate based on Xbox LIVE QoS" );
static ConVar mm_clientrateupdate_adjust( "mm_clientrateupdate_adjust", "0.6", 0, "Downstream rate adjustment" );
static ConVar mm_clientrateupdate_minimum( "mm_clientrateupdate_minimum", "20000", 0, "Minimum supported rate, Xbox TCR requires 40kbps" );
static ConVar mm_clientrateupdate_maximum( "mm_clientrateupdate_maximum", "30000", 0, "Maximum supported rate" );
static ConVar mm_clientrateupdate_qos_timeout( "mm_clientrateupdate_qos_timeout", "20", 0, "How long to wait for QOS to be determined" );
static void AdjustClientRateBasedOnQoS( DWORD dwDnBitsPerSec )
{
if ( !mm_clientrateupdate_enabled.GetBool() )
return;
static ConVarRef cl_rate( "rate" );
int desiredRate = (int)( ( dwDnBitsPerSec / 8.0f ) * mm_clientrateupdate_adjust.GetFloat() );
desiredRate = clamp( desiredRate, mm_clientrateupdate_minimum.GetInt(), mm_clientrateupdate_maximum.GetInt() );
// Update the client rate
ConColorMsg( Color( 255, 0, 255, 255 ), "[QoS] Bandwidth %d bps, Updating client rate to %d\n", dwDnBitsPerSec, desiredRate );
cl_rate.SetValue( desiredRate );
}
struct RateAdjustmentAsyncCall
{
// X360 peer
XNADDR apxna;
XNADDR const *papxna;
XNKID apxnkid;
XNKID const *papxnkid;
XNKEY apxnkey;
XNKEY const *papxnkey;
// XLSP server
IN_ADDR ina;
DWORD dwServiceId;
// QOS handle
XNQOS *pQOS;
// Time when QOS probe started
float flTimeStarted;
}
*g_pRateAdjustmentAsyncCall = NULL;
void MatchSession_RateAdjustmentUpdate_Release()
{
if ( !g_pRateAdjustmentAsyncCall )
return;
if ( g_pRateAdjustmentAsyncCall->pQOS )
g_pMatchExtensions->GetIXOnline()->XNetQosRelease( g_pRateAdjustmentAsyncCall->pQOS );
delete g_pRateAdjustmentAsyncCall;
g_pRateAdjustmentAsyncCall = NULL;
}
// Keeps adjusting client side rate setting based on QOS with server
void MatchSession_RateAdjustmentUpdate()
{
if ( !g_pRateAdjustmentAsyncCall )
return;
if ( g_pRateAdjustmentAsyncCall->pQOS->cxnqosPending &&
Plat_FloatTime() < g_pRateAdjustmentAsyncCall->flTimeStarted + mm_clientrateupdate_qos_timeout.GetFloat() )
return;
ConColorMsg( Color( 255, 0, 255, 255 ), "[QoS] Rate adjustment query %s\n", g_pRateAdjustmentAsyncCall->pQOS->cxnqosPending ? "timed out" : "completed" );
// QOS finished or timed out
XNQOSINFO &xni = g_pRateAdjustmentAsyncCall->pQOS->axnqosinfo[0];
AdjustClientRateBasedOnQoS( xni.dwDnBitsPerSec );
MatchSession_RateAdjustmentUpdate_Release();
}
void MatchSession_RateAdjustmentUpdate_Start( IN_ADDR const &ina )
{
MatchSession_RateAdjustmentUpdate_Release();
g_pRateAdjustmentAsyncCall = new RateAdjustmentAsyncCall;
ZeroMemory( g_pRateAdjustmentAsyncCall, sizeof( *g_pRateAdjustmentAsyncCall ) );
g_pRateAdjustmentAsyncCall->ina = ina;
g_pRateAdjustmentAsyncCall->dwServiceId = g_pMatchFramework->GetMatchTitle()->GetTitleServiceID();
g_pRateAdjustmentAsyncCall->flTimeStarted = Plat_FloatTime();
ConColorMsg( Color( 255, 0, 255, 255 ), "[QoS] Rate adjustment query scheduled for XLSP server: %08X\n", ina.s_addr );
INT ret = g_pMatchExtensions->GetIXOnline()->XNetQosLookup(
0, NULL, NULL, NULL,
1, &g_pRateAdjustmentAsyncCall->ina, &g_pRateAdjustmentAsyncCall->dwServiceId,
2, 0, 0, NULL, &g_pRateAdjustmentAsyncCall->pQOS );
if ( ret != ERROR_SUCCESS )
{
g_pRateAdjustmentAsyncCall->flTimeStarted = 0.0f;
}
}
void MatchSession_RateAdjustmentUpdate_Start( XSESSION_INFO const &xsi )
{
MatchSession_RateAdjustmentUpdate_Release();
g_pRateAdjustmentAsyncCall = new RateAdjustmentAsyncCall;
ZeroMemory( g_pRateAdjustmentAsyncCall, sizeof( *g_pRateAdjustmentAsyncCall ) );
g_pRateAdjustmentAsyncCall->apxna = xsi.hostAddress;
g_pRateAdjustmentAsyncCall->papxna = &g_pRateAdjustmentAsyncCall->apxna;
g_pRateAdjustmentAsyncCall->apxnkid = xsi.sessionID;
g_pRateAdjustmentAsyncCall->papxnkid = &g_pRateAdjustmentAsyncCall->apxnkid;
g_pRateAdjustmentAsyncCall->apxnkey = xsi.keyExchangeKey;
g_pRateAdjustmentAsyncCall->papxnkey = &g_pRateAdjustmentAsyncCall->apxnkey;
g_pRateAdjustmentAsyncCall->flTimeStarted = Plat_FloatTime();
ConColorMsg( Color( 255, 0, 255, 255 ), "[QoS] Rate adjustment query scheduled for Xbox 360 peer: %08X/%08X\n", xsi.hostAddress.ina.s_addr, xsi.hostAddress.inaOnline.s_addr );
INT ret = g_pMatchExtensions->GetIXOnline()->XNetQosLookup(
1, &g_pRateAdjustmentAsyncCall->papxna, &g_pRateAdjustmentAsyncCall->papxnkid, &g_pRateAdjustmentAsyncCall->papxnkey,
0, NULL, NULL,
2, 0, 0, NULL, &g_pRateAdjustmentAsyncCall->pQOS );
if ( ret != ERROR_SUCCESS )
{
g_pRateAdjustmentAsyncCall->flTimeStarted = 0.0f;
}
}
#endif
void MatchSession_BroadcastSessionSettingsUpdate( KeyValues *pUpdateDeletePackage )
{
KeyValues *notify = new KeyValues( "OnMatchSessionUpdate" );
notify->SetString( "state", "updated" );
if ( KeyValues *kvUpdate = pUpdateDeletePackage->FindKey( "update" ) )
notify->AddSubKey( kvUpdate->MakeCopy() );
if ( KeyValues *kvDelete = pUpdateDeletePackage->FindKey( "delete" ) )
notify->AddSubKey( kvDelete->MakeCopy() );
g_pMatchEventsSubscription->BroadcastEvent( notify );
}
ConVar cl_session( "cl_session", "", FCVAR_USERINFO | FCVAR_HIDDEN | FCVAR_SERVER_CAN_EXECUTE | FCVAR_DEVELOPMENTONLY );
void MatchSession_PrepareClientForConnect( KeyValues *pSettings, uint64 uiReservationCookieOverride )
{
char chSession[64];
sprintf( chSession, "$%llx", uiReservationCookieOverride ? uiReservationCookieOverride :
g_pMatchFramework->GetMatchSession()->GetSessionSystemData()->
GetUint64( "xuidReserve", 0ull ) );
cl_session.SetValue( chSession );
g_pMatchFramework->GetMatchTitle()->PrepareClientForConnect( pSettings );
}
static bool MatchSession_ResolveServerInfo_Helper_DsResult( KeyValues *pSettings, CSysSessionBase *pSysSession,
MatchSessionServerInfo_t &info, uint uiResolveFlags, uint64 ullCrypt )
{
#ifdef _X360
// On dedicated servers host should have given us an insecure
// address representing our Title Server
char const *szInsecureServerAddr = pSettings->GetString( "server/adrInsecure" );
netadr_t inetInsecure;
inetInsecure.SetFromString( szInsecureServerAddr );
IN_ADDR inaddrInsecure;
inaddrInsecure.s_addr = inetInsecure.GetIPNetworkByteOrder();
if ( ( uiResolveFlags & ( info.RESOLVE_DSRESULT | info.RESOLVE_QOS_RATE_PROBE ) ) == info.RESOLVE_QOS_RATE_PROBE )
{
// We are not required to resolve the DSRESULT, just submit the QOS rate probe
MatchSession_RateAdjustmentUpdate_Start( inaddrInsecure );
return true;
}
char const *szServerType = pSettings->GetString( "server/server", "listen" );
if ( !Q_stricmp( szServerType, "listen" ) )
{
info.m_dsResult.m_bDedicated = false;
return true;
}
if ( !Q_stricmp( szServerType, "externalpeer" ) )
{
info.m_dsResult.m_bDedicated = false;
return true;
}
Q_strncpy( info.m_dsResult.m_szInsecureSendableServerAddress,
szInsecureServerAddr,
ARRAYSIZE( info.m_dsResult.m_szInsecureSendableServerAddress ) );
// Map it to a secure address
IN_ADDR inaddrSecure;
DWORD ret = ERROR_FUNCTION_FAILED;
if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
{
inaddrSecure = inaddrInsecure;
ret = ERROR_SUCCESS;
}
else
{
ret = g_pMatchExtensions->GetIXOnline()->XNetServerToInAddr( inaddrInsecure, g_pMatchFramework->GetMatchTitle()->GetTitleServiceID(), &inaddrSecure );
}
if ( ret != ERROR_SUCCESS )
{
DevWarning( "Failed to resolve XLSP secure address (code = 0x%08X, insecure = %s/%s)!\n",
ret, inetInsecure.ToString(), szInsecureServerAddr );
return false;
}
else
{
netadr_t inetSecure = inetInsecure;
inetSecure.SetIP( inaddrSecure.s_addr );
DevMsg( "Resolved XLSP secure address %s, insecure address was %s.\n",
inetSecure.ToString(), szInsecureServerAddr );
Q_strncpy( info.m_dsResult.m_szConnectionString,
inetSecure.ToString(),
ARRAYSIZE( info.m_dsResult.m_szConnectionString ) );
info.m_dsResult.m_bDedicated = true;
// Start QOS rate calculation for the dedicated XLSP server
MatchSession_RateAdjustmentUpdate_Start( inaddrInsecure );
}
#elif !defined( NO_STEAM )
char const *szAddress = pSettings->GetString( "server/adronline", "0.0.0.0" );
if ( char const *szDecrypted = MatchSession_DecryptAddressString( szAddress, ullCrypt ) )
szAddress = szDecrypted;
Q_strncpy( info.m_dsResult.m_szPublicConnectionString, szAddress,
ARRAYSIZE( info.m_dsResult.m_szPublicConnectionString ) );
szAddress = pSettings->GetString( "server/adrlocal", "0.0.0.0" );
if ( char const *szDecrypted = MatchSession_DecryptAddressString( szAddress, ullCrypt ) )
szAddress = szDecrypted;
Q_strncpy( info.m_dsResult.m_szPrivateConnectionString, szAddress,
ARRAYSIZE( info.m_dsResult.m_szPrivateConnectionString ) );
#endif
return true;
}
static bool MatchSession_ResolveServerInfo_Helper_ConnectString( KeyValues *pSettings, CSysSessionBase *pSysSession, MatchSessionServerInfo_t &info, uint uiResolveFlags )
{
//
// Prepare the connect command
//
#ifdef _X360
char const *szServerType = pSettings->GetString( "server/server", "listen" );
if ( !Q_stricmp( "externalpeer", szServerType ) && !( uiResolveFlags & info.RESOLVE_ALLOW_EXTPEER ) )
pSysSession = NULL;
char const *szConnectionString = info.m_dsResult.m_szConnectionString;
if ( info.m_dsResult.m_bDedicated )
{
info.m_szSecureServerAddress = info.m_dsResult.m_szConnectionString;
}
else if ( CSysSessionClient *pSysSessionClient = dynamic_cast< CSysSessionClient * >( pSysSession ) )
{
XSESSION_INFO xsi = {0};
szConnectionString = pSysSessionClient->GetHostNetworkAddress( xsi );
if ( !szConnectionString )
{
DevWarning( "MatchSession_ResolveServerInfo_Helper_ConnectString::GetHostNetworkAddress failed!\n" );
return false;
}
// Start QOS rate calculation for our session host X360 xnaddr
MatchSession_RateAdjustmentUpdate_Start( xsi );
}
else if ( char const *szSessionInfo = pSettings->GetString( "server/sessioninfo", NULL ) )
{
// We don't have a dedicated server and don't allow to use external peer directly,
// register security keys
XSESSION_INFO xsi = {0};
MMX360_SessionInfoFromString( xsi, szSessionInfo );
// Resolve XNADDR
IN_ADDR inaddrRemote;
g_pMatchExtensions->GetIXOnline()->XNetRegisterKey( &xsi.sessionID, &xsi.keyExchangeKey );
if ( int err = g_pMatchExtensions->GetIXOnline()->XNetXnAddrToInAddr( &xsi.hostAddress, &xsi.sessionID, &inaddrRemote ) )
{
DevWarning( "MatchSession_ResolveServerInfo_Helper_ConnectString::XNetXnAddrToInAddr"
" failed to resolve XNADDR ( code 0x%08X, sessioninfo = %s )\n",
err, szSessionInfo );
g_pMatchExtensions->GetIXOnline()->XNetUnregisterKey( &xsi.sessionID );
return false;
}
// Initiate secure connection and key exchange
if ( int err = g_pMatchExtensions->GetIXOnline()->XNetConnect( inaddrRemote ) )
{
DevWarning( "MatchSession_ResolveServerInfo_Helper_ConnectString::XNetConnect"
" failed to start key exchange ( code 0x%08X, sessioninfo = %s )\n",
err, szSessionInfo );
// Secure IN_ADDR associations are removed implicitly when their key gets unregistered
g_pMatchExtensions->GetIXOnline()->XNetUnregisterKey( &xsi.sessionID );
return false;
}
//
// Prepare connection string
//
netadr_t inetAddr;
inetAddr.SetType( NA_IP );
inetAddr.SetIPAndPort( inaddrRemote.s_addr, 0 );
// Now we know the address for the game to connect
Q_strncpy( info.m_dsResult.m_szConnectionString, inetAddr.ToString( true ), ARRAYSIZE( info.m_dsResult.m_szConnectionString ) );
//
// Remember all the settings needed to deallocate the secure association
//
info.m_szSecureServerAddress = info.m_dsResult.m_szInsecureSendableServerAddress;
Q_snprintf( info.m_dsResult.m_szInsecureSendableServerAddress,
ARRAYSIZE( info.m_dsResult.m_szInsecureSendableServerAddress ),
"SESSIONINFO %s", szSessionInfo );
// Start QOS rate calculation for opponents session host X360 remote xnaddr
MatchSession_RateAdjustmentUpdate_Start( xsi );
}
else
return false;
Q_snprintf( info.m_szConnectCmd, sizeof( info.m_szConnectCmd ),
"connect_splitscreen %s %s %d\n",
szConnectionString,
szConnectionString,
XBX_GetNumGameUsers() );
#elif !defined( NO_STEAM )
Q_snprintf( info.m_szConnectCmd, sizeof( info.m_szConnectCmd ),
"connect %s %s\n",
info.m_dsResult.m_szPublicConnectionString,
info.m_dsResult.m_szPrivateConnectionString );
#endif
info.m_xuidJingle = pSettings->GetUint64( "server/xuid", 0ull );
if ( uint64 uiReservationCookieOverride = pSettings->GetUint64( "server/reservationid", 0ull ) )
info.m_uiReservationCookie = uiReservationCookieOverride;
else if ( pSysSession )
info.m_uiReservationCookie = pSysSession->GetReservationCookie();
else
info.m_uiReservationCookie = 0ull;
return true;
}
bool MatchSession_ResolveServerInfo( KeyValues *pSettings, CSysSessionBase *pSysSession, MatchSessionServerInfo_t &info, uint uiResolveFlags, uint64 ullCrypt )
{
if ( ( uiResolveFlags & ( info.RESOLVE_DSRESULT | info.RESOLVE_QOS_RATE_PROBE ) ) &&
!MatchSession_ResolveServerInfo_Helper_DsResult( pSettings, pSysSession, info, uiResolveFlags, ullCrypt ) )
return false;
if ( ( uiResolveFlags & info.RESOLVE_CONNECTSTRING ) &&
!MatchSession_ResolveServerInfo_Helper_ConnectString( pSettings, pSysSession, info, uiResolveFlags ) )
return false;
return true;
}
ConVar mm_tu_string( "mm_tu_string", "00000000" );
uint64 MatchSession_GetMachineFlags()
{
uint64 uiFlags = 0;
if ( IsPS3() )
uiFlags |= MACHINE_PLATFORM_PS3;
return uiFlags;
}
char const * MatchSession_GetTuInstalledString()
{
return mm_tu_string.GetString();
}
char const * MatchSession_EncryptAddressString( char const *szAddress, uint64 ullCrypt )
{
if ( !szAddress || !*szAddress )
return NULL;
if ( !ullCrypt )
return NULL;
if ( szAddress[0] == ':' )
return NULL;
if ( szAddress[ 0 ] == '$' )
return NULL;
static unsigned char s_chData[256];
int nLen = Q_strlen( szAddress );
if ( nLen >= ARRAYSIZE( s_chData )/2 - 1 )
return NULL;
// Copy the address
s_chData[0] = '$';
for ( int j = 0; j < nLen; ++ j )
{
uint8 uiVal = uint8( szAddress[j] ) ^ uint8( reinterpret_cast< uint8 * >(&ullCrypt)[ j % sizeof( uint64 ) ] );
Q_snprintf( (char*)( s_chData + 1 + 2*j ), 3, "%02X", ( uint32 ) uiVal );
}
return (char*) s_chData;
}
char const * MatchSession_DecryptAddressString( char const *szAddress, uint64 ullCrypt )
{
if ( !szAddress || !*szAddress )
return NULL;
if ( !ullCrypt )
return NULL;
if ( szAddress[ 0 ] != '$' )
return NULL;
static unsigned char s_chData[ 256 ];
int nLen = Q_strlen( szAddress );
if ( nLen*2 + 2 >= ARRAYSIZE( s_chData ) )
return NULL;
// Copy the address
for ( int j = 0; j < nLen/2; ++j )
{
uint32 uiVal;
if ( !sscanf( szAddress + 1 + 2*j, "%02X", &uiVal ) )
return NULL;
if ( uiVal > 0xFF )
return NULL;
uiVal = uint8( uiVal ) ^ uint8( reinterpret_cast< uint8 * >(&ullCrypt)[ j % sizeof( uint64 ) ] );
if ( !uiVal )
return NULL;
s_chData[j] = uiVal;
}
s_chData[nLen/2] = 0;
return (char*) s_chData;
}
CON_COMMAND( mm_debugprint, "Show debug information about current matchmaking session" )
{
if ( IMatchSession *pIMatchSession = g_pMMF->GetMatchSession() )
{
( ( IMatchSessionInternal * ) pIMatchSession )->DebugPrint();
}
else
{
DevMsg( "No match session.\n" );
}
}

96
matchmaking/mm_session.h Normal file
View File

@@ -0,0 +1,96 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_SESSION_H
#define MM_SESSION_H
#ifdef _WIN32
#pragma once
#endif
class IMatchSessionInternal : public IMatchSession, public IMatchEventsSink
{
public:
// Run a frame update
virtual void Update() = 0;
// Destroy the session object
virtual void Destroy() = 0;
// Debug print a session object
virtual void DebugPrint() = 0;
// Check if another session is joinable
virtual bool IsAnotherSessionJoinable( char const *pszAnotherSessionInfo ) = 0;
};
#include "protocol.h"
#ifndef SWDS
#include "sys_session.h"
#include "x360_xlsp_cmd.h"
#include "ds_searcher.h"
#include "match_searcher.h"
#include "mm_session_offline_custom.h"
#include "mm_session_online_host.h"
#include "mm_session_online_client.h"
#include "mm_session_online_search.h"
#include "mm_session_online_teamsearch.h"
void MatchSession_BroadcastSessionSettingsUpdate( KeyValues *pUpdateDeletePackage );
void MatchSession_PrepareClientForConnect( KeyValues *pSettings, uint64 uiReservationCookieOverride = 0ull );
struct MatchSessionServerInfo_t
{
CDsSearcher::DsResult_t m_dsResult;
char m_szConnectCmd[256];
char const *m_szSecureServerAddress;
XUID m_xuidJingle;
uint64 m_uiReservationCookie;
enum ResolveFlags_t
{
RESOLVE_DSRESULT = 0x01,
RESOLVE_CONNECTSTRING = 0x02,
RESOLVE_ALLOW_EXTPEER = 0x04,
RESOLVE_QOS_RATE_PROBE = 0x08,
};
enum ResolveMasks_t
{
RESOLVE_DEFAULT = RESOLVE_DSRESULT | RESOLVE_CONNECTSTRING | RESOLVE_QOS_RATE_PROBE,
};
};
bool MatchSession_ResolveServerInfo( KeyValues *pSettings, CSysSessionBase *pSysSession,
MatchSessionServerInfo_t &info, uint uiResolveFlags = MatchSessionServerInfo_t::RESOLVE_DEFAULT,
uint64 ullCrypt = 0ull );
uint64 MatchSession_GetMachineFlags();
char const * MatchSession_GetTuInstalledString();
enum MatchSessionMachineFlags_t
{
MACHINE_PLATFORM_PS3 = ( 1 << 0 ), // Machine is PS3
};
char const * MatchSession_EncryptAddressString( char const *szAddress, uint64 ullCrypt );
char const * MatchSession_DecryptAddressString( char const *szAddress, uint64 ullCrypt );
#endif // SWDS
#ifdef _X360
// Keeps adjusting client side rate setting based on QOS with server
void MatchSession_RateAdjustmentUpdate();
#endif
#endif // MM_SESSION_H

View File

@@ -0,0 +1,332 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_framework.h"
#include "fmtstr.h"
#include "netmessages_signon.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//
// CMatchSessionOfflineCustom
//
// Implementation of an offline session
// that allows customization before the actual
// game commences (like playing commentary mode
// or playing single-player)
//
CMatchSessionOfflineCustom::CMatchSessionOfflineCustom( KeyValues *pSettings ) :
m_pSettings( pSettings->MakeCopy() ),
m_autodelete_pSettings( m_pSettings ),
m_eState( STATE_INIT ),
m_bExpectingServerReload( false )
{
DevMsg( "Created CMatchSessionOfflineCustom:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
InitializeGameSettings();
}
CMatchSessionOfflineCustom::~CMatchSessionOfflineCustom()
{
DevMsg( "Destroying CMatchSessionOfflineCustom:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
KeyValues * CMatchSessionOfflineCustom::GetSessionSettings()
{
return m_pSettings;
}
void CMatchSessionOfflineCustom::UpdateSessionSettings( KeyValues *pSettings )
{
// Extend the update keys
g_pMMF->GetMatchTitleGameSettingsMgr()->ExtendGameSettingsUpdateKeys( m_pSettings, pSettings );
m_pSettings->MergeFrom( pSettings );
// Broadcast the update to everybody interested
MatchSession_BroadcastSessionSettingsUpdate( pSettings );
}
void CMatchSessionOfflineCustom::UpdateTeamProperties( KeyValues *pTeamProperties )
{
}
void CMatchSessionOfflineCustom::Command( KeyValues *pCommand )
{
char const *szCommand = pCommand->GetName();
if ( !Q_stricmp( "Start", szCommand ) && m_eState < STATE_RUNNING )
{
m_eState = STATE_RUNNING;
OnGamePrepareLobbyForGame();
UpdateSessionSettings( KeyValues::AutoDeleteInline( KeyValues::FromString(
"update",
" update { "
" server { "
" server listen "
" } "
" } "
) ) );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnProfilesWriteOpportunity", "reason", "sessionstart"
) );
bool bResult = g_pMatchFramework->GetMatchTitle()->StartServerMap( m_pSettings );
if ( !bResult )
{
Warning( "Failed to start server map!\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
Assert( 0 );
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "error", "error", "nomap" ) );
}
Msg( "Succeeded in starting server map!\n" );
return;
}
if ( !Q_stricmp( "QueueConnect", szCommand ) )
{
char const *szConnectAddress = pCommand->GetString( "adronline", "0.0.0.0" );
uint64 uiReservationId = pCommand->GetUint64( "reservationid" );
bool bAutoCloseSession = pCommand->GetBool( "auto_close_session" );
Assert( bAutoCloseSession );
if ( bAutoCloseSession )
{
// Switch the state
m_eState = STATE_RUNNING;
MatchSession_PrepareClientForConnect( m_pSettings, uiReservationId );
// Close the session, potentially resetting a bunch of state
if ( bAutoCloseSession )
g_pMatchFramework->CloseSession();
// Determine reservation settings required
g_pMatchExtensions->GetINetSupport()->UpdateClientReservation( uiReservationId, 0ull );
// Issue the connect command
g_pMatchExtensions->GetIVEngineClient()->StartLoadingScreenForCommand( CFmtStr( "connect %s", szConnectAddress ) );
return;
}
}
//
// Let the title-specific matchmaking handle the command
//
CUtlVector< KeyValues * > arrPlayersUpdated;
arrPlayersUpdated.SetCount( m_pSettings->GetInt( "members/numPlayers", 0 ) );
memset( arrPlayersUpdated.Base(), 0, arrPlayersUpdated.Count() * sizeof( KeyValues * ) );
g_pMMF->GetMatchTitleGameSettingsMgr()->ExecuteCommand( pCommand, GetSessionSystemData(), m_pSettings, arrPlayersUpdated.Base() );
// Now notify the framework about player updated
for ( int k = 0; k < arrPlayersUpdated.Count(); ++ k )
{
if ( !arrPlayersUpdated[k] )
break;
// Notify the framework about player updated
KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" );
kvEvent->SetUint64( "xuid", arrPlayersUpdated[k]->GetUint64( "xuid" ) );
g_pMatchEventsSubscription->BroadcastEvent( kvEvent );
}
//
// Send the command as event for handling
//
KeyValues *pEvent = pCommand->MakeCopy();
pEvent->SetName( CFmtStr( "Command::%s", pCommand->GetName() ) );
g_pMatchEventsSubscription->BroadcastEvent( pEvent );
}
uint64 CMatchSessionOfflineCustom::GetSessionID()
{
return 0;
}
void CMatchSessionOfflineCustom::Update()
{
switch ( m_eState )
{
case STATE_INIT:
m_eState = STATE_CONFIG;
// Let everybody know that the session is now ready
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "ready", "transition", "offlineinit" ) );
break;
}
}
void CMatchSessionOfflineCustom::Destroy()
{
if ( m_eState == STATE_RUNNING )
{
g_pMatchExtensions->GetIVEngineClient()->ExecuteClientCmd( "disconnect" );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnProfilesWriteOpportunity", "reason", "sessionend"
) );
}
delete this;
}
void CMatchSessionOfflineCustom::DebugPrint()
{
DevMsg( "CMatchSessionOfflineCustom [ state=%d ]\n", m_eState );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
void CMatchSessionOfflineCustom::OnEvent( KeyValues *pEvent )
{
char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "OnEngineClientSignonStateChange", szEvent ) )
{
int iOldState = pEvent->GetInt( "old", 0 );
int iNewState = pEvent->GetInt( "new", 0 );
if ( iOldState >= SIGNONSTATE_CONNECTED &&
iNewState < SIGNONSTATE_CONNECTED )
{
// Disconnecting from server
DevMsg( "OnEngineClientSignonStateChange\n" );
if ( m_bExpectingServerReload )
{
m_bExpectingServerReload = false;
DevMsg( " session was expecting server reload...\n" );
return;
}
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "mmF->CloseSession" ) );
return;
}
}
else if ( !Q_stricmp( "OnEngineClientSignonStatePrepareChange", szEvent ) )
{
char const *szReason = pEvent->GetString( "reason" );
if ( !Q_stricmp( "reload", szReason ) )
{
Assert( !m_bExpectingServerReload );
m_bExpectingServerReload = true;
return;
}
else if ( !Q_stricmp( "load", szReason ) )
{
char const *szLevelName = g_pMatchExtensions->GetIVEngineClient()->GetLevelName();
if ( szLevelName && szLevelName[0] && g_pMatchExtensions->GetIVEngineClient()->IsConnected() )
{
Assert( !m_bExpectingServerReload );
m_bExpectingServerReload = true;
return;
}
}
}
else if ( !Q_stricmp( "OnEngineEndGame", szEvent ) )
{
DevMsg( "OnEngineEndGame\n" );
// Issue the disconnect command
g_pMatchExtensions->GetIVEngineClient()->ExecuteClientCmd( "disconnect" );
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "mmF->CloseSession" ) );
return;
}
}
void CMatchSessionOfflineCustom::InitializeGameSettings()
{
// Since the session can be created with a minimal amount of data available
// the session object is responsible for initializing the missing data to defaults
// or saved values or values from gamer progress/profile or etc...
if ( KeyValues *kv = m_pSettings->FindKey( "system", true ) )
{
kv->SetString( "network", "offline" );
kv->SetString( "access", "public" );
}
if ( KeyValues *kv = m_pSettings->FindKey( "options", true ) )
{
kv->SetString( "server", "listen" );
}
if ( KeyValues *pMembers = m_pSettings->FindKey( "members", true ) )
{
pMembers->SetInt( "numMachines", 1 );
int numPlayers = 1;
#ifdef _GAMECONSOLE
numPlayers = XBX_GetNumGameUsers();
#endif
pMembers->SetInt( "numPlayers", numPlayers );
pMembers->SetInt( "numSlots", numPlayers );
if ( KeyValues *pMachine = pMembers->FindKey( "machine0", true ) )
{
IPlayerLocal *pPriPlayer = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() );
pMachine->SetUint64( "id", ( pPriPlayer ? pPriPlayer->GetXUID() : INVALID_XUID ) );
pMachine->SetUint64( "flags", MatchSession_GetMachineFlags() );
pMachine->SetInt( "numPlayers", numPlayers );
pMachine->SetUint64( "dlcmask", g_pMatchFramework->GetMatchSystem()->GetDlcManager()->GetDataInfo()->GetUint64( "@info/installed" ) );
pMachine->SetString( "tuver", MatchSession_GetTuInstalledString() );
pMachine->SetInt( "ping", 0 );
for ( int k = 0; k < numPlayers; ++ k )
{
if ( KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", k ), true ) )
{
int iController = 0;
#ifdef _GAMECONSOLE
iController = XBX_GetUserId( k );
#endif
IPlayerLocal *player = g_pPlayerManager->GetLocalPlayer( iController );
if ( player )
{
pPlayer->SetUint64( "xuid", player->GetXUID() );
pPlayer->SetString( "name", player->GetName() );
}
}
}
}
}
// Let the title extend the game settings
g_pMMF->GetMatchTitleGameSettingsMgr()->InitializeGameSettings( m_pSettings, "host" );
DevMsg( "CMatchSessionOfflineCustom::InitializeGameSettings adjusted settings:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
void CMatchSessionOfflineCustom::OnGamePrepareLobbyForGame()
{
// Remember which players will get updated
CUtlVector< KeyValues * > arrPlayersUpdated;
arrPlayersUpdated.SetCount( m_pSettings->GetInt( "members/numPlayers", 0 ) );
memset( arrPlayersUpdated.Base(), 0, arrPlayersUpdated.Count() * sizeof( KeyValues * ) );
g_pMMF->GetMatchTitleGameSettingsMgr()->PrepareLobbyForGame( m_pSettings, arrPlayersUpdated.Base() );
// Notify the framework of the updates
for ( int k = 0; k < arrPlayersUpdated.Count(); ++ k )
{
if ( !arrPlayersUpdated[k] )
break;
// Notify the framework about player updated
KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" );
kvEvent->SetUint64( "xuid", arrPlayersUpdated[k]->GetUint64( "xuid" ) );
g_pMatchEventsSubscription->BroadcastEvent( kvEvent );
}
// Let the title prepare for connect
MatchSession_PrepareClientForConnect( m_pSettings );
}

View File

@@ -0,0 +1,86 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_SESSION_OFFLINE_CUSTOM_H
#define MM_SESSION_OFFLINE_CUSTOM_H
#ifdef _WIN32
#pragma once
#endif
//
// CMatchSessionOfflineCustom
//
// Implementation of an offline session
// that allows customization before the actual
// game commences (like playing commentary mode
// or playing single-player)
//
class CMatchSessionOfflineCustom : public IMatchSessionInternal
{
// Methods of IMatchSession
public:
// Get an internal pointer to session system-specific data
virtual KeyValues * GetSessionSystemData() { return NULL; }
// Get an internal pointer to session settings
virtual KeyValues * GetSessionSettings();
// Update session settings, only changing keys and values need
// to be passed and they will be updated
virtual void UpdateSessionSettings( KeyValues *pSettings );
virtual void UpdateTeamProperties( KeyValues *pTeamProperties );
virtual uint64 GetSessionID();
// Issue a session command
virtual void Command( KeyValues *pCommand );
// Run a frame update
virtual void Update();
// Destroy the session object
virtual void Destroy();
// Debug print a session object
virtual void DebugPrint();
// Check if another session is joinable
virtual bool IsAnotherSessionJoinable( char const *pszAnotherSessionInfo ) { return true; }
// Process event
virtual void OnEvent( KeyValues *pEvent );
public:
explicit CMatchSessionOfflineCustom( KeyValues *pSettings );
~CMatchSessionOfflineCustom();
protected:
void InitializeGameSettings();
//
// Overrides
//
protected:
void OnGamePrepareLobbyForGame();
protected:
KeyValues *m_pSettings;
KeyValues::AutoDelete m_autodelete_pSettings;
enum State_t
{
STATE_INIT,
STATE_CONFIG,
STATE_RUNNING
};
State_t m_eState;
bool m_bExpectingServerReload;
};
#endif

View File

@@ -0,0 +1,639 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_framework.h"
#include "vstdlib/random.h"
#include "fmtstr.h"
#include "netmessages_signon.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//
// CMatchSessionOnlineClient
//
// Implementation of an online session of a host machine
//
void CMatchSessionOnlineClient::Init()
{
KeyValues *psessionHostDataUnpacked = m_pSettings->FindKey( "sessionHostDataUnpacked" );
if ( psessionHostDataUnpacked )
{
m_pSettings->SetPtr( "options/sessionHostData", psessionHostDataUnpacked );
}
}
CMatchSessionOnlineClient::CMatchSessionOnlineClient( KeyValues *pSettings ) :
m_pSettings( pSettings->MakeCopy() ),
m_autodelete_pSettings( m_pSettings ),
m_pSysData( new KeyValues( "SysSessionData", "type", "client" ) ),
m_autodelete_pSysData( m_pSysData ),
m_eState( STATE_INIT ),
m_pSysSession( NULL )
{
DevMsg( "Created CMatchSessionOnlineClient:\n" );
Init();
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
InitializeGameSettings();
}
CMatchSessionOnlineClient::CMatchSessionOnlineClient( CSysSessionClient *pSysSession, KeyValues *pSettings ) :
m_pSettings( pSettings ),
m_autodelete_pSettings( m_pSettings ),
m_pSysData( new KeyValues( "SysSessionData", "type", "client" ) ),
m_autodelete_pSysData( m_pSysData ),
m_eState( STATE_LOBBY ),
m_pSysSession( pSysSession )
{
DevMsg( "Converted sys session into CMatchSessionOnlineClient:\n" );
Init();
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
CMatchSessionOnlineClient::CMatchSessionOnlineClient( CSysSessionHost *pSysSession, KeyValues *pExtendedSettings ) :
m_pSettings( pExtendedSettings->FindKey( "settings" ) ),
m_autodelete_pSettings( m_pSettings ),
m_pSysData( new KeyValues( "SysSessionData", "type", "client" ) ),
m_autodelete_pSysData( m_pSysData ),
m_eState( STATE_LOBBY ), // it's at least lobby, we'll figure out later
m_pSysSession( NULL )
{
DevMsg( "Migrating into CMatchSessionOnlineClient...\n" );
Init();
Assert( m_pSettings );
KeyValues::AutoDelete autodelete( pExtendedSettings );
pExtendedSettings->RemoveSubKey( m_pSettings ); // it's now our settings
// Install our session
g_pMMF->SetCurrentMatchSession( this );
// Check if the game is in a non-lobby state
char const *szState = pExtendedSettings->GetString( "state", "" );
if ( !Q_stricmp( szState, "game" ) )
m_eState = STATE_GAME;
else if ( !Q_stricmp( szState, "ending" ) )
m_eState = STATE_ENDING;
// Now we need to create the system session to reflect the client session passed
m_pSysSession = new CSysSessionClient( pSysSession, m_pSettings );
pSysSession->Destroy();
// Show the state
DevMsg( "Migrated into CMatchSessionOnlineClient:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
CMatchSessionOnlineClient::~CMatchSessionOnlineClient()
{
DevMsg( "Destroying CMatchSessionOnlineClient:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
KeyValues * CMatchSessionOnlineClient::GetSessionSystemData()
{
// Setup our sys data
m_pSysData->SetUint64( "xuidReserve", m_pSysSession ? m_pSysSession->GetReservationCookie() : 0ull );
m_pSysData->SetUint64( "xuidHost", m_pSysSession ? m_pSysSession->GetHostXuid() : 0ull );
switch ( m_eState )
{
case STATE_LOBBY:
m_pSysData->SetString( "state", "lobby" );
break;
case STATE_GAME:
m_pSysData->SetString( "state", "game" );
break;
default:
m_pSysData->SetString( "state", "" );
break;
}
return m_pSysData;
}
KeyValues * CMatchSessionOnlineClient::GetSessionSettings()
{
return m_pSettings;
}
void CMatchSessionOnlineClient::UpdateSessionSettings( KeyValues *pSettings )
{
// Avoid a warning and assert for queue state manipulation
if ( pSettings->GetFirstSubKey()->GetString( "game/mmqueue", NULL ) )
return;
// Otherwise warn
Warning( "CMatchSessionOnlineClient::UpdateSessionSettings is unavailable in state %d!\n", m_eState );
Assert( !"CMatchSessionOnlineClient::UpdateSessionSettings is unavailable!\n" );
}
void CMatchSessionOnlineClient::UpdateTeamProperties( KeyValues *pSettings )
{
m_pSysSession->UpdateTeamProperties( pSettings );
}
void CMatchSessionOnlineClient::Command( KeyValues *pCommand )
{
char const *szCommand, *szRun;
szCommand = pCommand->GetName();
szRun = pCommand->GetString( "run", "" );
if ( !Q_stricmp( szRun, "host" ) || !Q_stricmp( szRun, "all" ) || !Q_stricmp( szRun, "xuid" ) )
{
if ( m_pSysSession )
{
m_pSysSession->Command( pCommand );
return;
}
}
else if ( !*szRun || !Q_stricmp( szRun, "local" ) )
{
OnRunCommand( pCommand );
return;
}
Warning( "CMatchSessionOnlineClient::Command( %s ) unhandled!\n", szCommand );
Assert( !"CMatchSessionOnlineClient::Command" );
}
void CMatchSessionOnlineClient::OnRunCommand( KeyValues *pCommand )
{
char const *szCommand = pCommand->GetName();
if ( !Q_stricmp( "Migrate", szCommand ) )
{
if ( m_pSysSession ) // TODO: research who sends the "Migrate" command and how secure it is?
{
m_pSysSession->Migrate( pCommand );
return;
}
}
if ( !Q_stricmp( "QueueConnect", szCommand ) )
{
if ( ( m_eState == STATE_LOBBY ) && !pCommand->GetUint64( "_remote_xuidsrc" ) )
{
OnRunCommand_QueueConnect( pCommand );
return;
}
}
// Client-side command cannot update the players
g_pMMF->GetMatchTitleGameSettingsMgr()->ExecuteCommand( pCommand, GetSessionSystemData(), m_pSettings, NULL );
// Send the command as event for handling
KeyValues *pEvent = pCommand->MakeCopy();
pEvent->SetName( CFmtStr( "Command::%s", pCommand->GetName() ) );
g_pMatchEventsSubscription->BroadcastEvent( pEvent );
}
uint64 CMatchSessionOnlineClient::GetSessionID()
{
if( m_pSysSession )
{
return m_pSysSession->GetSessionID();
}
return 0;
}
void CMatchSessionOnlineClient::Update()
{
switch ( m_eState )
{
case STATE_INIT:
m_eState = STATE_CREATING;
// Adjust invite-based settings
uint64 uiInviteFlags = g_pMMF->GetLastInviteFlags();
m_pSettings->SetUint64( "members/joinflags", uiInviteFlags );
// Session is creating
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "joining"
) );
// Trigger session creation
m_pSysSession = new CSysSessionClient( m_pSettings );
break;
}
if ( m_pSysSession )
{
m_pSysSession->Update();
}
}
void CMatchSessionOnlineClient::Destroy()
{
if ( m_eState != STATE_MIGRATE )
{
g_pMatchExtensions->GetINetSupport()->UpdateClientReservation( 0ull, 0ull );
}
if ( m_eState == STATE_GAME || m_eState == STATE_ENDING )
{
m_eState = STATE_INIT; // the object is being deleted, but prevent signon state change handlers
g_pMatchExtensions->GetIVEngineClient()->ExecuteClientCmd( "disconnect" );
}
if ( m_pSysSession )
{
m_pSysSession->Destroy();
m_pSysSession = NULL;
}
delete this;
}
void CMatchSessionOnlineClient::DebugPrint()
{
DevMsg( "CMatchSessionOnlineClient [ state=%d ]\n", m_eState );
DevMsg( "System data:\n" );
KeyValuesDumpAsDevMsg( GetSessionSystemData(), 1 );
DevMsg( "Settings data:\n" );
KeyValuesDumpAsDevMsg( GetSessionSettings(), 1 );
if ( m_pSysSession )
m_pSysSession->DebugPrint();
else
DevMsg( "SysSession is NULL\n" );
}
bool CMatchSessionOnlineClient::IsAnotherSessionJoinable( const char *pszAnotherSessionInfo )
{
#ifdef _X360
if ( m_pSysSession )
{
XSESSION_INFO xsi;
if ( m_pSysSession->GetHostNetworkAddress( xsi ) )
{
XSESSION_INFO xsiAnother;
MMX360_SessionInfoFromString( xsiAnother, pszAnotherSessionInfo );
if ( !memcmp( &xsiAnother.sessionID, &xsi.sessionID, sizeof( xsi.sessionID ) ) )
return false;
}
}
#endif
return true;
}
void CMatchSessionOnlineClient::OnEvent( KeyValues *pEvent )
{
char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "OnEngineClientSignonStateChange", szEvent ) )
{
int iOldState = pEvent->GetInt( "old", 0 );
int iNewState = pEvent->GetInt( "new", 0 );
if ( iOldState >= SIGNONSTATE_CONNECTED &&
iNewState < SIGNONSTATE_CONNECTED )
{
if ( m_eState == STATE_GAME )
{
// Lost connection from server or explicit disconnect
DevMsg( "OnEngineClientSignonStateChange\n" );
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "mmF->CloseSession" ) );
}
else if ( m_eState == STATE_ENDING )
{
m_eState = STATE_LOBBY;
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "ready", "transition", "clientendgame" ) );
}
}
}
else if ( !Q_stricmp( "OnEngineDisconnectReason", szEvent ) )
{
if ( m_eState == STATE_GAME )
{
// Lost connection from server or explicit disconnect
char const *szReason = pEvent->GetString( "reason", "" );
DevMsg( "OnEngineDisconnectReason %s\n", szReason );
bool bLobbySalvagable =
StringHasPrefix( szReason, "Connection to server timed out" ) ||
StringHasPrefix( szReason, "Server shutting down" );
if ( KeyValues *pDisconnectHdlr = g_pMMF->GetMatchTitleGameSettingsMgr()->PrepareClientLobbyForGameDisconnect( m_pSettings, pEvent ) )
{
KeyValues::AutoDelete autodelete( pDisconnectHdlr );
char const *szDisconnectHdlr = pDisconnectHdlr->GetString( "disconnecthdlr", "" );
if ( !Q_stricmp( szDisconnectHdlr, "destroy" ) )
bLobbySalvagable = false;
else if ( !Q_stricmp( szDisconnectHdlr, "lobby" ) )
bLobbySalvagable = true;
}
if ( !bLobbySalvagable )
{
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "mmF->CloseSession" ) );
}
else
{
// Server shutting down, try to retain the lobby
pEvent->SetString( "disconnecthdlr", "lobby" );
g_pMatchEventsSubscription->RegisterEventData( pEvent->MakeCopy() );
OnEndGameToLobby();
}
}
}
else if ( !Q_stricmp( "OnEngineEndGame", szEvent ) )
{
if ( m_eState == STATE_GAME )
{
OnEndGameToLobby();
}
}
else if ( !Q_stricmp( "OnMatchSessionUpdate", szEvent ) )
{
char const *szState = pEvent->GetString( "state", "" );
if ( !Q_stricmp( "updated", szState ) )
{
switch ( m_eState )
{
case STATE_LOBBY:
if ( KeyValues *pServer = pEvent->FindKey( "update/server" ) )
{
// If we are in a con team match we only connect to game servers
// in response to an invite from the host so don't do anything
KeyValues *pConTeamMatch = m_pSettings->FindKey("options/conteammatch");
if ( !pConTeamMatch ||
( pEvent->GetUint64( "update/server/xuid" ) && pEvent->GetUint64( "update/server/reservationid" ) ) )
{
// Game server has become available - connect to it!
ConnectGameServer();
}
}
break;
case STATE_GAME:
if ( KeyValues *pServer = pEvent->FindKey( "delete/server" ) )
{
// Lobby leader has disconnected from game server, disconnect too
OnEndGameToLobby();
}
break;
}
}
}
else if ( !Q_stricmp( "OnNetLanConnectionlessPacket", szEvent ) )
{
char const *szPacketType = pEvent->GetFirstTrueSubKey()->GetName();
if ( m_pSysSession && m_eState > STATE_CREATING &&
!Q_stricmp( szPacketType, "LanSearch" ) )
{
m_pSysSession->ReplyLanSearch( pEvent );
}
}
else if ( !Q_stricmp( "OnSysMuteListChanged", szEvent ) )
{
if ( m_pSysSession )
m_pSysSession->Voice_UpdateMutelist();
}
else if ( !Q_stricmp( "mmF->SysSessionUpdate", szEvent ) )
{
if ( m_pSysSession && pEvent->GetPtr( "syssession", NULL ) == m_pSysSession )
{
// This is our session
if ( char const *szError = pEvent->GetString( "error", NULL ) )
{
// Destroy the session
m_pSysSession->Destroy();
m_pSysSession = NULL;
m_eState = STATE_CREATING;
// Handle error
KeyValues *kvUpdateError = pEvent->MakeCopy();
kvUpdateError->SetName( "OnMatchSessionUpdate" );
kvUpdateError->SetString( "state", "error" );
g_pMatchEventsSubscription->BroadcastEvent( kvUpdateError );
return;
}
if ( uint64 ullCrypt = pEvent->GetUint64( "crypt" ) )
m_pSysData->SetUint64( "crypt", ullCrypt );
switch ( m_eState )
{
case STATE_CREATING:
// Session was creating
// Now we are at least in the lobby
m_eState = STATE_LOBBY;
OnClientFullyConnectedToSession();
return;
default:
if ( char const *szAction = pEvent->GetString( "action", NULL ) )
{
if ( !Q_stricmp( "host", szAction ) )
{
KeyValues *pExtendedSettings = new KeyValues( "ExtendedSettings" );
char const *szMigrateState = "lobby";
switch ( m_eState )
{
case STATE_GAME:
szMigrateState = "game";
break;
case STATE_ENDING:
szMigrateState = "ending";
break;
}
pExtendedSettings->SetString( "state", szMigrateState );
if ( uint64 ullCrypt = m_pSysData->GetUint64( "crypt" ) )
pExtendedSettings->SetUint64( "crypt", ullCrypt );
pExtendedSettings->AddSubKey( m_pSettings );
// Release ownership of the resources since new match session now owns them
m_pSettings = NULL;
m_autodelete_pSettings.Assign( NULL );
CSysSessionClient *pSysSession = m_pSysSession;
m_pSysSession = NULL;
// Destroy our instance and create the new match interface
m_eState = STATE_MIGRATE;
g_pMMF->SetCurrentMatchSession( NULL );
this->Destroy();
// Now we need to create the new host session that will install itself
IMatchSession *pNewHost = new CMatchSessionOnlineHost( pSysSession, pExtendedSettings );
Assert( g_pMMF->GetMatchSession() == pNewHost );
pNewHost;
return;
}
}
break;
}
DevWarning( "Unhandled mmF->SysSessionUpdate!\n" );
Assert( !"Unhandled mmF->SysSessionUpdate!\n" );
}
}
else if ( !Q_stricmp( "mmF->SysSessionCommand", szEvent ) )
{
if ( m_pSysSession && pEvent->GetPtr( "syssession", NULL ) == m_pSysSession )
{
KeyValues *pCommand = pEvent->GetFirstTrueSubKey();
if ( pCommand )
{
OnRunCommand( pCommand );
}
}
}
}
void CMatchSessionOnlineClient::OnClientFullyConnectedToSession()
{
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "created" ) );
// Check whether the game server is available (i.e. game in progress)
if ( KeyValues *pServer = m_pSettings->FindKey( "server" ) )
{
ConnectGameServer();
}
else
{
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "ready", "transition", "clientconnect" ) );
}
}
void CMatchSessionOnlineClient::OnEndGameToLobby()
{
m_eState = STATE_ENDING;
// Issue the disconnect command
g_pMatchExtensions->GetIVEngineClient()->ExecuteClientCmd( "disconnect" );
// Mark gameplay state as inactive
m_pSysSession->SetSessionActiveGameplayState( false, NULL );
}
void CMatchSessionOnlineClient::InitializeGameSettings()
{
// Initialize only the settings required to connect...
if ( KeyValues *kv = m_pSettings->FindKey( "system", true ) )
{
KeyValuesAddDefaultString( kv, "network", "LIVE" );
KeyValuesAddDefaultString( kv, "access", "public" );
}
if ( KeyValues *pMembers = m_pSettings->FindKey( "members", true ) )
{
pMembers->SetInt( "numMachines", 1 );
int numPlayers = 1;
#ifdef _GAMECONSOLE
numPlayers = XBX_GetNumGameUsers();
#endif
pMembers->SetInt( "numPlayers", numPlayers );
pMembers->SetInt( "numSlots", numPlayers );
if ( KeyValues *pMachine = pMembers->FindKey( "machine0", true ) )
{
XUID machineid = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
pMachine->SetUint64( "id", machineid );
#if defined( _PS3 ) && !defined( NO_STEAM )
pMachine->SetUint64( "psnid", steamapicontext->SteamUser()->GetConsoleSteamID().ConvertToUint64() );
#endif
pMachine->SetUint64( "flags", MatchSession_GetMachineFlags() );
pMachine->SetInt( "numPlayers", numPlayers );
pMachine->SetUint64( "dlcmask", g_pMatchFramework->GetMatchSystem()->GetDlcManager()->GetDataInfo()->GetUint64( "@info/installed" ) );
pMachine->SetString( "tuver", MatchSession_GetTuInstalledString() );
pMachine->SetInt( "ping", 0 );
for ( int k = 0; k < numPlayers; ++ k )
{
if ( KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", k ), true ) )
{
int iController = 0;
#ifdef _GAMECONSOLE
iController = XBX_GetUserId( k );
#endif
IPlayerLocal *player = g_pPlayerManager->GetLocalPlayer( iController );
pPlayer->SetUint64( "xuid", player->GetXUID() );
pPlayer->SetString( "name", player->GetName() );
}
}
}
}
// Let the title extend the game settings
g_pMMF->GetMatchTitleGameSettingsMgr()->InitializeGameSettings( m_pSettings, "client" );
DevMsg( "CMatchSessionOnlineClient::InitializeGameSettings adjusted settings:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
void CMatchSessionOnlineClient::OnRunCommand_QueueConnect( KeyValues *pCommand )
{
char const *szConnectAddress = pCommand->GetString( "adronline", "0.0.0.0" );
uint64 uiReservationId = pCommand->GetUint64( "reservationid" );
bool bAutoCloseSession = pCommand->GetBool( "auto_close_session" );
// Switch the state
m_eState = STATE_GAME;
MatchSession_PrepareClientForConnect( m_pSettings, uiReservationId );
// Mark gameplay state as active
m_pSysSession->SetSessionActiveGameplayState( true, szConnectAddress );
// Close the session, potentially resetting a bunch of state
if ( bAutoCloseSession )
g_pMatchFramework->CloseSession();
// Determine reservation settings required
g_pMatchExtensions->GetINetSupport()->UpdateClientReservation( uiReservationId, 0ull );
// Issue the connect command
g_pMatchExtensions->GetIVEngineClient()->StartLoadingScreenForCommand( CFmtStr( "connect %s", szConnectAddress ) );
}
void CMatchSessionOnlineClient::ConnectGameServer()
{
// Switch the state
m_eState = STATE_GAME;
MatchSession_PrepareClientForConnect( m_pSettings );
//
// Resolve server information
//
MatchSessionServerInfo_t msInfo = {0};
if ( !MatchSession_ResolveServerInfo( m_pSettings, m_pSysSession, msInfo, msInfo.RESOLVE_DEFAULT, m_pSysData->GetUint64( "crypt" ) ) )
{
// Destroy the session
m_pSysSession->Destroy();
m_pSysSession = NULL;
// Handle error
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate",
"state", "error", "error", "connect" ) );
return;
}
// Determine reservation settings required
g_pMatchExtensions->GetINetSupport()->UpdateClientReservation( msInfo.m_uiReservationCookie, msInfo.m_xuidJingle );
// Mark gameplay state as active
m_pSysSession->SetSessionActiveGameplayState( true, msInfo.m_szSecureServerAddress );
// Issue the connect command
g_pMatchExtensions->GetIVEngineClient()->ClientCmd_Unrestricted( msInfo.m_szConnectCmd );
}

View File

@@ -0,0 +1,96 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_SESSION_ONLINE_CLIENT_H
#define MM_SESSION_ONLINE_CLIENT_H
#ifdef _WIN32
#pragma once
#endif
//
// CMatchSessionOnlineClient
//
// Implementation of an online session of a client machine
//
class CMatchSessionOnlineClient : public IMatchSessionInternal
{
// Methods of IMatchSession
public:
// Get an internal pointer to session system-specific data
virtual KeyValues * GetSessionSystemData();
// Get an internal pointer to session settings
virtual KeyValues * GetSessionSettings();
// Update session settings, only changing keys and values need
// to be passed and they will be updated
virtual void UpdateSessionSettings( KeyValues *pSettings );
virtual void UpdateTeamProperties( KeyValues *pTeamProperties );
// Issue a session command
virtual void Command( KeyValues *pCommand );
virtual uint64 GetSessionID();
// Run a frame update
virtual void Update();
// Destroy the session object
virtual void Destroy();
// Debug print a session object
virtual void DebugPrint();
// Check if another session is joinable
virtual bool IsAnotherSessionJoinable( char const *pszAnotherSessionInfo );
// Process event
virtual void OnEvent( KeyValues *pEvent );
void Init();
public:
explicit CMatchSessionOnlineClient( KeyValues *pSettings );
explicit CMatchSessionOnlineClient( CSysSessionClient *pSysSession, KeyValues *pSettings );
explicit CMatchSessionOnlineClient( CSysSessionHost *pSysSession, KeyValues *pExtendedSettings );
~CMatchSessionOnlineClient();
public:
void OnClientFullyConnectedToSession();
void OnEndGameToLobby();
protected:
void InitializeGameSettings();
void OnRunCommand( KeyValues *pCommand );
void OnRunCommand_QueueConnect( KeyValues *pCommand );
void ConnectGameServer();
protected:
KeyValues *m_pSettings;
KeyValues::AutoDelete m_autodelete_pSettings;
KeyValues *m_pSysData;
KeyValues::AutoDelete m_autodelete_pSysData;
enum State_t
{
STATE_INIT,
STATE_CREATING,
STATE_LOBBY,
STATE_GAME,
STATE_ENDING,
STATE_MIGRATE
};
State_t m_eState;
CSysSessionClient *m_pSysSession;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,116 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_SESSION_ONLINE_HOST_H
#define MM_SESSION_ONLINE_HOST_H
#ifdef _WIN32
#pragma once
#endif
class CMatchSessionOnlineTeamSearch;
class CMatchSessionOnlineSearch;
//
// CMatchSessionOnlineHost
//
// Implementation of an online session of a host machine
//
class CMatchSessionOnlineHost : public IMatchSessionInternal
{
// Methods of IMatchSession
public:
// Get an internal pointer to session system-specific data
virtual KeyValues * GetSessionSystemData();
// Get an internal pointer to session settings
virtual KeyValues * GetSessionSettings();
// Update session settings, only changing keys and values need
// to be passed and they will be updated
virtual void UpdateSessionSettings( KeyValues *pSettings );
virtual void UpdateTeamProperties( KeyValues *pTeamProperties );
// Issue a session command
virtual void Command( KeyValues *pCommand );
virtual uint64 GetSessionID();
// Run a frame update
virtual void Update();
// Destroy the session object
virtual void Destroy();
// Debug print a session object
virtual void DebugPrint();
// Check if another session is joinable
virtual bool IsAnotherSessionJoinable( char const *pszAnotherSessionInfo );
// Process event
virtual void OnEvent( KeyValues *pEvent );
public:
explicit CMatchSessionOnlineHost( KeyValues *pSettings );
explicit CMatchSessionOnlineHost( CSysSessionClient *pSysSession, KeyValues *pExtendedSettings );
~CMatchSessionOnlineHost();
protected:
void InitializeGameSettings();
void MigrateGameSettings();
void OnRunCommand( KeyValues *pCommand );
void OnRunCommand_Start();
void OnRunCommand_StartDsSearchFinished();
void OnRunCommand_StartListenServerStarted( uint32 externalIP );
void OnRunCommand_Cancel_DsSearch();
void OnRunCommand_Match();
void OnRunCommand_QueueConnect( KeyValues *pCommand );
void OnRunCommand_Cancel_Match();
void ConnectGameServer( CDsSearcher::DsResult_t *pDsResult );
void StartListenServerMap();
void OnEndGameToLobby();
void SetSessionActiveGameplayState( bool bActive, char const *szSecureServerAddress );
void InviteTeam();
//
// Overrides
//
protected:
virtual void OnGamePrepareLobbyForGame();
virtual void OnGamePlayerMachinesConnected( int numMachines );
protected:
KeyValues *m_pSettings;
KeyValues::AutoDelete m_autodelete_pSettings;
KeyValues *m_pSysData;
KeyValues::AutoDelete m_autodelete_pSysData;
enum State_t
{
STATE_INIT,
STATE_CREATING,
STATE_LOBBY,
STATE_MATCHING, // host is running matchmaking for opponents
STATE_MATCHINGRESTART, // team communication failed and matching needs to restart
STATE_STARTING, // host is running a dedicated search or other things before actual game server kicks in
STATE_LOADING, // host is starting a server map
STATE_GAME,
STATE_ENDING,
STATE_MIGRATE
};
State_t m_eState;
CSysSessionHost *m_pSysSession;
CDsSearcher *m_pDsSearcher;
CMatchSessionOnlineTeamSearch *m_pTeamSearcher;
CMatchSessionOnlineSearch *m_pMatchSearcher;
};
#endif

View File

@@ -0,0 +1,771 @@
//===== Copyright 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_framework.h"
#include "vstdlib/random.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static ConVar mm_ignored_sessions_forget_time( "mm_ignored_sessions_forget_time", "600", FCVAR_DEVELOPMENTONLY );
static ConVar mm_ignored_sessions_forget_pass( "mm_ignored_sessions_forget_pass", "5", FCVAR_DEVELOPMENTONLY );
class CIgnoredSessionsMgr
{
public:
CIgnoredSessionsMgr();
public:
void Reset();
void OnSearchStarted();
bool IsIgnored( XNKID xid );
void Ignore( XNKID xid );
protected:
struct SessionSearchPass_t
{
double m_flTime;
int m_nSearchCounter;
};
static bool XNKID_LessFunc( const XNKID &lhs, const XNKID &rhs )
{
return ( (uint64 const&) lhs ) < ( (uint64 const&) rhs );
}
CUtlMap< XNKID, SessionSearchPass_t > m_IgnoredSessionsAndTime;
int m_nSearchCounter;
};
static CIgnoredSessionsMgr g_IgnoredSessionsMgr;
static CUtlMap< uint32, float > g_mapValidatedWhitelistCacheTimestamps( DefLessFunc( uint32 ) );
CON_COMMAND_F( mm_ignored_sessions_reset, "Reset ignored sessions", FCVAR_DEVELOPMENTONLY )
{
g_IgnoredSessionsMgr.Reset();
DevMsg( "Reset ignored sessions" );
}
CIgnoredSessionsMgr::CIgnoredSessionsMgr() :
m_IgnoredSessionsAndTime( XNKID_LessFunc ),
m_nSearchCounter( 0 )
{
}
void CIgnoredSessionsMgr::Reset()
{
m_nSearchCounter = 0;
m_IgnoredSessionsAndTime.RemoveAll();
}
void CIgnoredSessionsMgr::OnSearchStarted()
{
++ m_nSearchCounter;
double fNow = Plat_FloatTime();
double const fKeepIgnoredTime = mm_ignored_sessions_forget_time.GetFloat();
int const numIgnoredSearches = mm_ignored_sessions_forget_pass.GetInt();
// Keep sessions for only so long...
for ( int x = m_IgnoredSessionsAndTime.FirstInorder();
x != m_IgnoredSessionsAndTime.InvalidIndex(); )
{
SessionSearchPass_t ssp = m_IgnoredSessionsAndTime.Element( x );
int xNext = m_IgnoredSessionsAndTime.NextInorder( x );
if ( fabs( fNow - ssp.m_flTime ) > fKeepIgnoredTime &&
m_nSearchCounter - ssp.m_nSearchCounter > numIgnoredSearches )
{
m_IgnoredSessionsAndTime.RemoveAt( x );
}
x = xNext;
}
}
bool CIgnoredSessionsMgr::IsIgnored( XNKID xid )
{
return ( m_IgnoredSessionsAndTime.Find( xid ) != m_IgnoredSessionsAndTime.InvalidIndex() );
}
void CIgnoredSessionsMgr::Ignore( XNKID xid )
{
if ( ( const uint64 & )xid == 0ull )
return;
SessionSearchPass_t ssp = { Plat_FloatTime(), m_nSearchCounter };
m_IgnoredSessionsAndTime.InsertOrReplace( xid, ssp );
}
//
// CMatchSessionOnlineSearch
//
// Implementation of an online session search (aka matchmaking)
//
CMatchSessionOnlineSearch::CMatchSessionOnlineSearch( KeyValues *pSettings ) :
m_pSettings( pSettings->MakeCopy() ),
m_autodelete_pSettings( m_pSettings ),
m_eState( STATE_INIT ),
m_pSysSession( NULL ),
m_pMatchSearcher( NULL ),
m_result( RESULT_UNDEFINED ),
m_pSysSessionConTeam (NULL),
#if !defined( NO_STEAM )
m_pServerListListener( NULL ),
#endif
m_flInitializeTimestamp( 0.0f )
{
DevMsg( "Created CMatchSessionOnlineSearch:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
CMatchSessionOnlineSearch::CMatchSessionOnlineSearch() :
m_pSettings( NULL ),
m_autodelete_pSettings( (KeyValues*)NULL ),
m_eState( STATE_INIT ),
m_pSysSession( NULL ),
m_pMatchSearcher( NULL ),
m_result( RESULT_UNDEFINED ),
m_pSysSessionConTeam (NULL),
m_flInitializeTimestamp( 0.0f )
{
}
CMatchSessionOnlineSearch::~CMatchSessionOnlineSearch()
{
if ( m_pMatchSearcher )
m_pMatchSearcher->Destroy();
m_pMatchSearcher = NULL;
DevMsg( "Destroying CMatchSessionOnlineSearch:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
KeyValues * CMatchSessionOnlineSearch::GetSessionSettings()
{
return m_pSettings;
}
void CMatchSessionOnlineSearch::UpdateSessionSettings( KeyValues *pSettings )
{
Warning( "CMatchSessionOnlineSearch::UpdateSessionSettings is unavailable in state %d!\n", m_eState );
Assert( !"CMatchSessionOnlineSearch::UpdateSessionSettings is unavailable!\n" );
}
void CMatchSessionOnlineSearch::Command( KeyValues *pCommand )
{
Warning( "CMatchSessionOnlineSearch::Command is unavailable!\n" );
Assert( !"CMatchSessionOnlineSearch::Command is unavailable!\n" );
}
uint64 CMatchSessionOnlineSearch::GetSessionID()
{
return 0;
}
#if !defined( NO_STEAM )
extern volatile uint32 *g_hRankingSetupCallHandle;
void CMatchSessionOnlineSearch::SetupSteamRankingConfiguration()
{
KeyValues *kvNotification = new KeyValues( "SetupSteamRankingConfiguration" );
kvNotification->SetPtr( "settingsptr", m_pSettings );
kvNotification->SetPtr( "callhandleptr", ( void * ) &g_hRankingSetupCallHandle );
g_pMatchEventsSubscription->BroadcastEvent( kvNotification );
}
bool CMatchSessionOnlineSearch::IsSteamRankingConfigured() const
{
return !g_hRankingSetupCallHandle || !*g_hRankingSetupCallHandle;
}
#endif
extern ConVar mm_session_sys_ranking_timeout;
extern ConVar mm_session_search_qos_timeout;
void CMatchSessionOnlineSearch::Update()
{
switch ( m_eState )
{
case STATE_INIT:
if ( !m_flInitializeTimestamp )
{
m_flInitializeTimestamp = Plat_FloatTime();
#if !defined( NO_STEAM )
SetupSteamRankingConfiguration();
#endif
}
#if !defined( NO_STEAM )
if ( !IsSteamRankingConfigured() && ( Plat_FloatTime() < m_flInitializeTimestamp + mm_session_sys_ranking_timeout.GetFloat() ) )
break;
#endif
m_eState = STATE_SEARCHING;
// Kick off the search
g_IgnoredSessionsMgr.OnSearchStarted();
m_pMatchSearcher = OnStartSearching();
// Update our settings with match searcher
m_pSettings->deleteThis();
m_pSettings = m_pMatchSearcher->GetSearchSettings()->MakeCopy();
m_autodelete_pSettings.Assign( m_pSettings );
// Run the first frame update on the searcher
m_pMatchSearcher->Update();
break;
case STATE_SEARCHING:
// Waiting for session search to complete
m_pMatchSearcher->Update();
break;
case STATE_JOIN_NEXT:
StartJoinNextFoundSession();
break;
#if !defined( NO_STEAM )
case STATE_VALIDATING_WHITELIST:
if ( Plat_FloatTime() > m_flInitializeTimestamp + mm_session_search_qos_timeout.GetFloat() )
{
DevWarning( "Steam whitelist validation timed out.\n" );
Steam_OnDedicatedServerListFetched();
}
break;
#endif
case STATE_JOINING:
// Waiting for the join negotiation
if ( m_pSysSession )
{
m_pSysSession->Update();
}
if (m_pSysSessionConTeam)
{
m_pSysSessionConTeam->Update();
switch ( m_pSysSessionConTeam->GetResult() )
{
case CSysSessionConTeamHost::RESULT_SUCCESS:
OnSearchCompletedSuccess( NULL, m_pSettings );
break;
case CSysSessionConTeamHost::RESULT_FAIL:
m_pSysSessionConTeam->Destroy();
m_pSysSessionConTeam = NULL;
// Try next session
m_eState = STATE_JOIN_NEXT;
break;
}
}
break;
}
}
void CMatchSessionOnlineSearch::Destroy()
{
// Stop the search
if ( m_pMatchSearcher )
{
m_pMatchSearcher->Destroy();
m_pMatchSearcher = NULL;
}
// If we are in the middle of connecting,
// abort
if ( m_pSysSession )
{
m_pSysSession->Destroy();
m_pSysSession = NULL;
}
if ( m_pSysSessionConTeam )
{
m_pSysSessionConTeam->Destroy();
m_pSysSessionConTeam = NULL;
}
#if !defined( NO_STEAM )
if ( m_pServerListListener )
{
m_pServerListListener->Destroy();
m_pServerListListener = NULL;
}
#endif
delete this;
}
void CMatchSessionOnlineSearch::DebugPrint()
{
DevMsg( "CMatchSessionOnlineSearch [ state=%d ]\n", m_eState );
DevMsg( "System data:\n" );
KeyValuesDumpAsDevMsg( GetSessionSystemData(), 1 );
DevMsg( "Settings data:\n" );
KeyValuesDumpAsDevMsg( GetSessionSettings(), 1 );
if ( m_pSysSession )
m_pSysSession->DebugPrint();
else
DevMsg( "SysSession is NULL\n" );
DevMsg( "Search results outstanding: %d\n", m_arrSearchResults.Count() );
}
void CMatchSessionOnlineSearch::OnEvent( KeyValues *pEvent )
{
char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "mmF->SysSessionUpdate", szEvent ) )
{
if ( m_pSysSession && pEvent->GetPtr( "syssession", NULL ) == m_pSysSession )
{
// This is our session
switch ( m_eState )
{
case STATE_JOINING:
// Session was creating
if ( char const *szError = pEvent->GetString( "error", NULL ) )
{
// Destroy the session
m_pSysSession->Destroy();
m_pSysSession = NULL;
// Go ahead and join next available session
m_eState = STATE_JOIN_NEXT;
}
else
{
// We have received an entirely new "settings" data and copied that to our "settings" data
m_eState = STATE_CLOSING;
// Now we need to create a new client session
CSysSessionClient *pSysSession = m_pSysSession;
KeyValues *pSettings = m_pSettings;
// Release ownership of the resources since new match session now owns them
m_pSysSession = NULL;
m_pSettings = NULL;
m_autodelete_pSettings.Assign( NULL );
OnSearchCompletedSuccess( pSysSession, pSettings );
return;
}
break;
}
}
}
}
void CMatchSessionOnlineSearch::OnSearchEvent( KeyValues *pNotify )
{
g_pMatchEventsSubscription->BroadcastEvent( pNotify );
}
CSysSessionClient * CMatchSessionOnlineSearch::OnBeginJoiningSearchResult()
{
return new CSysSessionClient( m_pSettings );
}
void CMatchSessionOnlineSearch::OnSearchDoneNoResultsMatch()
{
m_eState = STATE_CLOSING;
// Reset ignored session tracker
g_IgnoredSessionsMgr.Reset();
// Just go ahead and create the session
KeyValues *pSettings = m_pSettings;
m_pSettings = NULL;
m_autodelete_pSettings.Assign( NULL );
OnSearchCompletedEmpty( pSettings );
}
void CMatchSessionOnlineSearch::OnSearchCompletedSuccess( CSysSessionClient *pSysSession, KeyValues *pSettings )
{
m_result = RESULT_SUCCESS;
// Note that m_pSysSessionConTeam will be NULL if this is an individual joining a
// match that was started with con team
KeyValues *teamMatch = pSettings->FindKey( "options/conteammatch" );
if ( teamMatch && m_pSysSessionConTeam )
{
DevMsg( "OnlineSearch - ConTeam host reserved session\n" );
KeyValuesDumpAsDevMsg( pSettings );
int numPlayers, sides[10];
uint64 playerIds[10];
if ( !m_pSysSessionConTeam->GetPlayerSidesAssignment( &numPlayers, playerIds, sides ) )
{
// Something went badly wrong, bail
m_result = RESULT_FAIL;
}
else
{
// Send out a "joinsession" event. This is picked up by the host and sent out
// to all machines machines in lobby.
KeyValues *joinSession = new KeyValues(
"OnMatchSessionUpdate",
"state", "joinconteamsession"
);
#if defined (_X360)
uint64 sessionId = pSettings->GetUint64( "options/sessionid", 0 );
const char *sessionInfo = pSettings->GetString( "options/sessioninfo", "" );
joinSession->SetUint64( "sessionid", sessionId );
joinSession->SetString( "sessioninfo", sessionInfo );
// Unpack sessionHostData
KeyValues *pSessionHostData = (KeyValues*)pSettings->GetPtr( "options/sessionHostData" );
if ( pSessionHostData )
{
KeyValues *pSessionHostDataUnpacked = joinSession->CreateNewKey();
pSessionHostDataUnpacked->SetName("sessionHostDataUnpacked");
pSessionHostData->CopySubkeys( pSessionHostDataUnpacked );
}
#else
joinSession->SetUint64( "sessionid", m_pSysSessionConTeam->GetSessionID() );
#endif
KeyValues *pTeamMembers = joinSession->CreateNewKey();
pTeamMembers->SetName( "teamMembers" );
pTeamMembers->SetInt( "numPlayers", numPlayers );
// Assign players to different teams
for ( int i = 0; i < numPlayers; i++ )
{
KeyValues *pTeamPlayer = pTeamMembers->CreateNewKey();
pTeamPlayer->SetName( CFmtStr( "player%d", i ) );
pTeamPlayer->SetUint64( "xuid", playerIds[i] );
pTeamPlayer->SetInt( "team", sides[i] );
}
OnSearchEvent( joinSession );
}
}
else
{
// Destroy our instance and point at the new match interface
CMatchSessionOnlineClient *pNewSession = new CMatchSessionOnlineClient( pSysSession, pSettings );
g_pMMF->SetCurrentMatchSession( pNewSession );
this->Destroy();
DevMsg( "OnlineSearch - client fully connected to session, search finished.\n" );
pNewSession->OnClientFullyConnectedToSession();
}
}
void CMatchSessionOnlineSearch::OnSearchCompletedEmpty( KeyValues *pSettings )
{
KeyValues::AutoDelete autodelete_pSettings( pSettings );
m_result = RESULT_FAIL;
KeyValues *notify = new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "searchempty"
);
notify->SetPtr( "settingsptr", pSettings );
OnSearchEvent( notify );
if ( !Q_stricmp( pSettings->GetString( "options/searchempty" ), "close" ) )
{
g_pMatchFramework->CloseSession();
return;
}
// If this is a team session then stop here and let the team host decide what
// to do next
KeyValues *teamMatch = pSettings->FindKey( "options/conteammatch" );
if ( teamMatch )
{
return;
}
// Preserve the "options/bypasslobby" key
bool bypassLobby = pSettings->GetBool( "options/bypasslobby", false );
// Preserve the "options/server" key
char serverType[64];
const char *prevServerType = pSettings->GetString( "options/server", NULL );
if ( prevServerType )
{
Q_strncpy( serverType, prevServerType, sizeof( serverType ) );
}
// Remove "options" key
if ( KeyValues *kvOptions = pSettings->FindKey( "options" ) )
{
pSettings->RemoveSubKey( kvOptions );
kvOptions->deleteThis();
}
pSettings->SetString( "options/createreason", "searchempty" );
if ( bypassLobby )
{
pSettings->SetBool( "options/bypasslobby", bypassLobby );
}
if ( prevServerType )
{
pSettings->SetString( "options/server", serverType );
}
DevMsg( "Search completed empty - creating a new session\n" );
KeyValuesDumpAsDevMsg( pSettings );
g_pMatchFramework->CreateSession( pSettings );
}
void CMatchSessionOnlineSearch::UpdateTeamProperties( KeyValues *pTeamProperties )
{
}
void CMatchSessionOnlineSearch::StartJoinNextFoundSession()
{
if ( !m_arrSearchResults.Count() )
{
OnSearchDoneNoResultsMatch();
return;
}
// Session is joining
KeyValues *notify = new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "searchresult"
);
notify->SetInt( "numResults", m_arrSearchResults.Count() );
OnSearchEvent( notify );
// Peek at the next search result
CMatchSearcher::SearchResult_t const &sr = *m_arrSearchResults.Head();
// Register it into the ignored session pool
g_IgnoredSessionsMgr.Ignore( sr.GetXNKID() );
// Make a validation query
ValidateSearchResultWhitelist();
}
static uint32 OfficialWhitelistClientCachedAddress( uint32 uiServerIP )
{
/** Removed for partner depot **/
return 0;
}
void CMatchSessionOnlineSearch::ValidateSearchResultWhitelist()
{
#if !defined( NO_STEAM )
// In case of official matchmaking we need to validate that the server
// that we are about to join actually is whitelisted official server
if ( !m_pSettings->GetInt( "game/hosted" ) )
{
// Peek at the next search result
CMatchSearcher::SearchResult_t const &sr = *m_arrSearchResults.Head();
if ( ( g_mapValidatedWhitelistCacheTimestamps.Find( sr.m_svAdr.GetIPHostByteOrder() ) == g_mapValidatedWhitelistCacheTimestamps.InvalidIndex() ) &&
!OfficialWhitelistClientCachedAddress( sr.m_svAdr.GetIPHostByteOrder() )
)
{
// This server needs to be validated
m_flInitializeTimestamp = Plat_FloatTime();
m_eState = STATE_VALIDATING_WHITELIST;
CUtlVector< MatchMakingKeyValuePair_t > filters;
filters.EnsureCapacity( 10 );
filters.AddToTail( MatchMakingKeyValuePair_t( "gamedir", COM_GetModDirectory() ) );
filters.AddToTail( MatchMakingKeyValuePair_t( "addr", sr.m_svAdr.ToString() ) );
filters.AddToTail( MatchMakingKeyValuePair_t( "white", "1" ) );
m_pServerListListener = new CServerListListener( this, filters );
return;
}
}
#endif
ConnectJoinLobbyNextFoundSession();
}
#if !defined( NO_STEAM )
CMatchSessionOnlineSearch::CServerListListener::CServerListListener( CMatchSessionOnlineSearch *pDsSearcher, CUtlVector< MatchMakingKeyValuePair_t > &filters ) :
m_pOuter( pDsSearcher ),
m_hRequest( NULL )
{
MatchMakingKeyValuePair_t *pFilter = filters.Base();
DevMsg( 1, "Requesting dedicated whitelist validation...\n" );
for (int i = 0; i < filters.Count(); i++)
{
DevMsg("Filter %d: %s=%s\n", i, filters.Element(i).m_szKey, filters.Element(i).m_szValue);
}
m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestInternetServerList(
( AppId_t ) g_pMatchFramework->GetMatchTitle()->GetTitleID(), &pFilter,
filters.Count(), this );
}
void CMatchSessionOnlineSearch::CServerListListener::Destroy()
{
m_pOuter = NULL;
if ( m_hRequest )
steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest );
m_hRequest = NULL;
delete this;
}
void CMatchSessionOnlineSearch::CServerListListener::HandleServerResponse( HServerListRequest hReq, int iServer, bool bResponded )
{
// Register the result
if ( bResponded )
{
gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers()
->GetServerDetails( hReq, iServer );
DevMsg( 1, "Successfully validated whitelist for %s...\n", pServer->m_NetAdr.GetConnectionAddressString() );
g_mapValidatedWhitelistCacheTimestamps.InsertOrReplace( pServer->m_NetAdr.GetIP(), Plat_FloatTime() );
}
}
void CMatchSessionOnlineSearch::CServerListListener::RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response )
{
if ( m_pOuter )
{
m_pOuter->Steam_OnDedicatedServerListFetched();
}
}
void CMatchSessionOnlineSearch::Steam_OnDedicatedServerListFetched()
{
if ( m_pServerListListener )
{
m_pServerListListener->Destroy();
m_pServerListListener = NULL;
}
// Peek at the next search result
if ( m_arrSearchResults.Count() )
{
CMatchSearcher::SearchResult_t const &sr = *m_arrSearchResults.Head();
if ( g_mapValidatedWhitelistCacheTimestamps.Find( sr.m_svAdr.GetIPHostByteOrder() ) == g_mapValidatedWhitelistCacheTimestamps.InvalidIndex() )
{
DevWarning( 1, "Failed to validate whitelist for %s...\n", sr.m_svAdr.ToString( true ) );
FOR_EACH_VEC_BACK( m_arrSearchResults, itSR )
{
if ( m_arrSearchResults[itSR]->m_svAdr.GetIPHostByteOrder() == sr.m_svAdr.GetIPHostByteOrder() )
{
m_arrSearchResults.Remove( itSR );
}
}
}
}
m_eState = STATE_JOIN_NEXT;
}
#endif
void CMatchSessionOnlineSearch::ConnectJoinLobbyNextFoundSession()
{
// Pop the search result
CMatchSearcher::SearchResult_t const &sr = *m_arrSearchResults.Head();
m_arrSearchResults.RemoveMultipleFromHead( 1 );
// Set the settings to connect with
if ( KeyValues *kvOptions = m_pSettings->FindKey( "options", true ) )
{
#ifdef _X360
kvOptions->SetUint64( "sessionid", ( const uint64 & ) sr.m_info.sessionID );
char chSessionInfo[ XSESSION_INFO_STRING_LENGTH ] = {0};
MMX360_SessionInfoToString( sr.m_info, chSessionInfo );
kvOptions->SetString( "sessioninfo", chSessionInfo );
kvOptions->SetPtr( "sessionHostData", sr.GetGameDetails() );
KeyValuesDumpAsDevMsg( sr.GetGameDetails(), 1, 2 );
#else
kvOptions->SetUint64( "sessionid", sr.m_uiLobbyId );
#endif
}
// Trigger client session creation
Msg( "[MM] Joining session %llx, %d search results remaining...\n",
m_pSettings->GetUint64( "options/sessionid", 0ull ),
m_arrSearchResults.Count() );
KeyValues *teamMatch = m_pSettings->FindKey( "options/conteammatch" );
if ( teamMatch )
{
m_pSysSessionConTeam = new CSysSessionConTeamHost( m_pSettings );
}
else
{
m_pSysSession = OnBeginJoiningSearchResult();
}
m_eState = STATE_JOINING;
}
CMatchSearcher_OnlineSearch::CMatchSearcher_OnlineSearch( CMatchSessionOnlineSearch *pSession, KeyValues *pSettings ) :
CMatchSearcher( pSettings ),
m_pSession( pSession )
{
}
void CMatchSearcher_OnlineSearch::OnSearchEvent( KeyValues *pNotify )
{
m_pSession->OnSearchEvent( pNotify );
}
void CMatchSearcher_OnlineSearch::OnSearchDone()
{
// Let the base searcher finalize results
CMatchSearcher::OnSearchDone();
// Iterate over search results
for ( int k = 0, kNum = GetNumSearchResults(); k < kNum; ++ k )
{
SearchResult_t const &sr = GetSearchResult( k );
if ( !g_IgnoredSessionsMgr.IsIgnored( sr.GetXNKID() ) )
m_pSession->m_arrSearchResults.AddToTail( &sr );
}
if ( !m_pSession->m_arrSearchResults.Count() )
{
m_pSession->OnSearchDoneNoResultsMatch();
return;
}
// Go ahead and start joining the results
DevMsg( "Establishing connection with %d search results.\n", m_pSession->m_arrSearchResults.Count() );
m_pSession->m_eState = m_pSession->STATE_JOIN_NEXT;
}
CMatchSearcher * CMatchSessionOnlineSearch::OnStartSearching()
{
CMatchSearcher *pMS = new CMatchSearcher_OnlineSearch( this, m_pSettings->MakeCopy() );
// Let the title extend the game settings
g_pMMF->GetMatchTitleGameSettingsMgr()->InitializeGameSettings( pMS->GetSearchSettings(), "search_online" );
DevMsg( "CMatchSearcher_OnlineSearch title adjusted settings:\n" );
KeyValuesDumpAsDevMsg( pMS->GetSearchSettings(), 1 );
return pMS;
}

View File

@@ -0,0 +1,161 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_SESSION_ONLINE_SEARCH_H
#define MM_SESSION_ONLINE_SEARCH_H
#ifdef _WIN32
#pragma once
#endif
//
// CMatchSessionOnlineSearch
//
// Implementation of an online session search (aka matchmaking)
//
class CMatchSearcher_OnlineSearch;
class CMatchSessionOnlineSearch : public IMatchSessionInternal
{
// Methods of IMatchSession
public:
// Get an internal pointer to session system-specific data
virtual KeyValues * GetSessionSystemData() { return NULL; }
// Get an internal pointer to session settings
virtual KeyValues * GetSessionSettings();
// Update session settings, only changing keys and values need
// to be passed and they will be updated
virtual void UpdateSessionSettings( KeyValues *pSettings );
virtual void UpdateTeamProperties( KeyValues *pTeamProperties );
// Issue a session command
virtual void Command( KeyValues *pCommand );
virtual uint64 GetSessionID();
// Run a frame update
virtual void Update();
// Destroy the session object
virtual void Destroy();
// Debug print a session object
virtual void DebugPrint();
// Check if another session is joinable
virtual bool IsAnotherSessionJoinable( char const *pszAnotherSessionInfo ) { return true; }
// Process event
virtual void OnEvent( KeyValues *pEvent );
enum Result
{
RESULT_UNDEFINED,
RESULT_SUCCESS,
RESULT_FAIL
};
Result GetResult() { return m_result; }
public:
explicit CMatchSessionOnlineSearch( KeyValues *pSettings );
virtual ~CMatchSessionOnlineSearch();
protected:
CMatchSessionOnlineSearch(); // for derived classes construction
//
// Overrides when search is used as a nested object
//
protected:
virtual CMatchSearcher *OnStartSearching();
virtual void OnSearchCompletedEmpty( KeyValues *pSettings );
virtual void OnSearchCompletedSuccess( CSysSessionClient *pSysSession, KeyValues *pSettings );
virtual void OnSearchEvent( KeyValues *pNotify );
virtual CSysSessionClient * OnBeginJoiningSearchResult();
protected:
void StartJoinNextFoundSession();
void ValidateSearchResultWhitelist();
void ConnectJoinLobbyNextFoundSession();
void OnSearchDoneNoResultsMatch();
protected:
KeyValues *m_pSettings;
KeyValues::AutoDelete m_autodelete_pSettings;
friend class CMatchSearcher_OnlineSearch;
CMatchSearcher *m_pMatchSearcher;
Result m_result;
enum State_t
{
STATE_INIT,
STATE_SEARCHING,
STATE_JOIN_NEXT,
#if !defined( NO_STEAM )
STATE_VALIDATING_WHITELIST,
#endif
STATE_JOINING,
STATE_CLOSING,
};
State_t m_eState;
CUtlVector< CMatchSearcher::SearchResult_t const * > m_arrSearchResults;
CSysSessionClient *m_pSysSession;
CSysSessionConTeamHost *m_pSysSessionConTeam;
#if !defined( NO_STEAM )
void SetupSteamRankingConfiguration();
bool IsSteamRankingConfigured() const;
class CServerListListener : public ISteamMatchmakingServerListResponse
{
public:
explicit CServerListListener( CMatchSessionOnlineSearch *pDsSearcher, CUtlVector< MatchMakingKeyValuePair_t > &filters );
void Destroy();
public:
// Server has responded ok with updated data
virtual void ServerResponded( HServerListRequest hReq, int iServer ) { HandleServerResponse( hReq, iServer, true ); }
// Server has failed to respond
virtual void ServerFailedToRespond( HServerListRequest hReq, int iServer ) { HandleServerResponse( hReq, iServer, false ); }
// A list refresh you had initiated is now 100% completed
virtual void RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response );
protected:
void HandleServerResponse( HServerListRequest hReq, int iServer, bool bResponded );
CMatchSessionOnlineSearch *m_pOuter;
HServerListRequest m_hRequest;
};
friend class CServerListListener;
CServerListListener *m_pServerListListener;
void Steam_OnDedicatedServerListFetched();
#endif
float m_flInitializeTimestamp;
};
class CMatchSearcher_OnlineSearch : public CMatchSearcher
{
public:
CMatchSearcher_OnlineSearch( CMatchSessionOnlineSearch *pSession, KeyValues *pSettings );
public:
virtual void OnSearchEvent( KeyValues *pNotify );
virtual void OnSearchDone();
protected:
CMatchSessionOnlineSearch *m_pSession;
};
#endif

View File

@@ -0,0 +1,858 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_framework.h"
#include "vstdlib/random.h"
#include "fmtstr.h"
#include "steam_datacenterjobs.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar mm_teamsearch_errortime( "mm_teamsearch_errortime", "3.0", FCVAR_DEVELOPMENTONLY, "Time team search is in error state until it self-cancels" );
ConVar mm_teamsearch_nostart( "mm_teamsearch_nostart", "0", FCVAR_DEVELOPMENTONLY, "Team search will fake cancel before searching for server" );
#ifndef _GAMECONSOLE
ConVar sv_search_team_key( "sv_search_team_key", "public", FCVAR_RELEASE, "When initiating team search, set this key to match with known opponents team" );
#endif
//
//
// Specialized implementation of SysSessions for team search
//
//
template < typename TBaseSession >
class CSysSessionStubForTeamSearch : public TBaseSession
{
public:
explicit CSysSessionStubForTeamSearch( KeyValues *pSettings, CMatchSessionOnlineTeamSearch *pMatchSession ) :
TBaseSession( pSettings ),
m_pMatchSession( pMatchSession )
{
}
protected:
virtual void OnSessionEvent( KeyValues *notify )
{
if ( !notify )
return;
if ( m_pMatchSession )
m_pMatchSession->OnSessionEvent( notify );
notify->deleteThis();
}
protected:
// No voice
virtual void Voice_ProcessTalkers( KeyValues *pMachine, bool bAdd ) {}
virtual void Voice_CaptureAndTransmitLocalVoiceData() {}
virtual void Voice_Playback( KeyValues *msg, XUID xuidSrc ) OVERRIDE {}
virtual void Voice_UpdateLocalHeadsetsStatus() {}
virtual void Voice_UpdateMutelist() {}
protected:
// No p2p connections, just two hosts talking
virtual void XP2P_Interconnect() {}
protected:
CMatchSessionOnlineTeamSearch *m_pMatchSession;
};
typedef CSysSessionStubForTeamSearch< CSysSessionClient > CSysTeamSearchClient;
typedef CSysSessionStubForTeamSearch< CSysSessionHost > CSysTeamSearchHost;
//
//
// CMatchSessionOnlineTeamSearch implementation
//
//
CMatchSessionOnlineTeamSearch::CMatchSessionOnlineTeamSearch( KeyValues *pSettings, CMatchSessionOnlineHost *pHost ) :
m_pHostSession( pHost ),
m_eState( STATE_SEARCHING ),
m_pSysSessionHost( NULL ),
m_pSysSessionClient( NULL ),
m_pDsSearcher( NULL ),
m_flActionTime( 0.0f ),
m_pUpdateHostSessionPacket( NULL ),
m_autodelete_pUpdateHostSessionPacket( m_pUpdateHostSessionPacket ),
m_iLinkState( 0 ),
m_xuidLinkPeer( 0ull ),
#ifdef _X360
m_pXlspConnection( NULL ),
m_pXlspCommandBatch( NULL ),
#endif
m_flCreationTime( Plat_FloatTime() )
{
m_pSettings = pSettings->MakeCopy();
m_autodelete_pSettings.Assign( m_pSettings );
#ifndef _GAMECONSOLE
m_pSettings->SetString( "options/searchteamkey", sv_search_team_key.GetString() );
#endif
if ( IsPC() )
{
// On PC limit server selection for team games to official and listen
if ( Q_stricmp( m_pSettings->GetString( "options/server" ), "listen" ) )
m_pSettings->SetString( "options/server", "official" );
}
DevMsg( "Created CMatchSessionOnlineTeamSearch:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
CMatchSessionOnlineTeamSearch::~CMatchSessionOnlineTeamSearch()
{
DevMsg( "Destroying CMatchSessionOnlineTeamSearch.\n" );
}
CMatchSearcher * CMatchSessionOnlineTeamSearch::OnStartSearching()
{
return new CMatchSearcher_OnlineTeamSearch( this, m_pSettings->MakeCopy() );
}
CMatchSearcher_OnlineTeamSearch::CMatchSearcher_OnlineTeamSearch( CMatchSessionOnlineTeamSearch *pSession, KeyValues *pSettings ) :
CMatchSearcher_OnlineSearch( pSession, pSettings )
{
// Search settings cannot be locked as it will interfere with syssessions
m_pSettings->SetString( "system/lock", "" );
// Team versus searchable lobbies are public
m_pSettings->SetString( "system/access", "public" );
// If we happen to host the session it will be a special teamlink session
m_pSettings->SetString( "system/netflag", "teamlink" );
if ( IMatchSession *pMainSession = g_pMatchFramework->GetMatchSession() )
{
KeyValues *kvSystemData = pMainSession->GetSessionSystemData();
m_pSettings->SetUint64( "System/dependentlobby", kvSystemData->GetUint64( "xuidReserve" ) );
}
// Double the number of slots since we assume two teams playing
m_pSettings->SetInt( "Members/numSlots", g_pMatchFramework->GetMatchTitle()->GetTotalNumPlayersSupported() );
// Let the title extend the game settings
g_pMMF->GetMatchTitleGameSettingsMgr()->InitializeGameSettings( m_pSettings, "search_onlineteam" );
DevMsg( "CMatchSearcher_OnlineTeamSearch title adjusted settings:\n" );
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
}
void CMatchSearcher_OnlineTeamSearch::StartSearchPass( KeyValues *pSearchPass )
{
#ifndef _GAMECONSOLE
pSearchPass->SetString( "Filter=/options:searchteamkey", sv_search_team_key.GetString() );
#endif
CMatchSearcher_OnlineSearch::StartSearchPass( pSearchPass );
}
void CMatchSessionOnlineTeamSearch::OnSearchEvent( KeyValues *pNotify )
{
if ( !pNotify )
return;
if ( m_pHostSession )
m_pHostSession->OnEvent( pNotify );
pNotify->deleteThis();
}
void CMatchSessionOnlineTeamSearch::OnSessionEvent( KeyValues *pEvent )
{
OnEvent( pEvent );
// Searching state is handled entirely by the base class
if ( m_eState == STATE_SEARCHING )
return;
char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "mmF->SysSessionUpdate", szEvent ) )
{
if ( m_pSysSessionHost && pEvent->GetPtr( "syssession", NULL ) == m_pSysSessionHost )
{
// We had a session error
if ( char const *szError = pEvent->GetString( "error", NULL ) )
{
// Destroy the session
m_pSysSessionHost->Destroy();
m_pSysSessionHost = NULL;
// Handle error
m_eState = STATE_ERROR;
m_flActionTime = Plat_FloatTime() + mm_teamsearch_errortime.GetFloat();
OnSearchEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "searcherror"
) );
return;
}
// This is our session
switch ( m_eState )
{
case STATE_CREATING:
// Session created successfully and we are awaiting peer
m_eState = STATE_AWAITING_PEER;
OnSearchEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "searchawaitingpeer"
) );
break;
}
}
}
else if ( !Q_stricmp( "mmF->SysSessionCommand", szEvent ) )
{
if ( m_pSysSessionHost && pEvent->GetPtr( "syssession", NULL ) == m_pSysSessionHost )
{
KeyValues *pCommand = pEvent->GetFirstTrueSubKey();
if ( pCommand )
{
OnRunSessionCommand( pCommand );
}
}
if ( m_pSysSessionClient && pEvent->GetPtr( "syssession", NULL ) == m_pSysSessionClient )
{
KeyValues *pCommand = pEvent->GetFirstTrueSubKey();
if ( pCommand )
{
OnRunSessionCommand( pCommand );
}
}
}
else if ( !Q_stricmp( "OnPlayerMachinesConnected", szEvent ) )
{
// Another team is challenging us
m_eState = STATE_LINK_HOST;
m_xuidLinkPeer = pEvent->GetUint64( "id" );
OnSearchEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "searchlinked"
) );
// Lock the session to prevent other teams challenges
m_pSettings->SetString( "system/lock", "linked" );
m_pSysSessionHost->OnUpdateSessionSettings( KeyValues::AutoDeleteInline( KeyValues::FromString(
"update",
" update { "
" system { "
" lock linked "
" } "
" } "
) ) );
LinkHost().LinkInit();
}
else if ( !Q_stricmp( "OnPlayerRemoved", szEvent ) )
{
// Assert( m_xuidLinkPeer == pEvent->GetUint64( "xuid" ) );
// the peer gave up before we finished negotiation, start the process all over
ResetAndRestartTeamSearch();
}
}
void CMatchSessionOnlineTeamSearch::ResetAndRestartTeamSearch()
{
m_eState = STATE_ERROR;
m_flActionTime = 0.0f;
OnSearchEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "restart"
) );
}
void CMatchSessionOnlineTeamSearch::RememberHostSessionUpdatePacket( KeyValues *pPacket )
{
if ( m_pUpdateHostSessionPacket )
m_pUpdateHostSessionPacket->deleteThis();
m_pUpdateHostSessionPacket = pPacket;
m_autodelete_pUpdateHostSessionPacket.Assign( m_pUpdateHostSessionPacket );
}
void CMatchSessionOnlineTeamSearch::ApplyHostSessionUpdatePacket()
{
if ( m_pUpdateHostSessionPacket )
{
KeyValues *pUpdatePkt = m_pUpdateHostSessionPacket;
m_pUpdateHostSessionPacket = NULL;
m_autodelete_pUpdateHostSessionPacket.Assign( NULL );
m_pHostSession->UpdateSessionSettings( pUpdatePkt );
pUpdatePkt->deleteThis();
}
}
void CMatchSessionOnlineTeamSearch::OnRunSessionCommand( KeyValues *pCommand )
{
switch ( m_eState )
{
case STATE_LINK_HOST:
LinkHost().LinkCommand( pCommand );
break;
case STATE_LINK_CLIENT:
LinkClient().LinkCommand( pCommand );
break;
}
}
CMatchSessionOnlineTeamSearchLinkHost & CMatchSessionOnlineTeamSearch::LinkHost()
{
return static_cast< CMatchSessionOnlineTeamSearchLinkHost & >( *this );
}
CMatchSessionOnlineTeamSearchLinkClient & CMatchSessionOnlineTeamSearch::LinkClient()
{
return static_cast< CMatchSessionOnlineTeamSearchLinkClient & >( *this );
}
CSysSessionBase * CMatchSessionOnlineTeamSearch::LinkSysSession()
{
switch ( m_eState )
{
case STATE_LINK_HOST:
return m_pSysSessionHost;
case STATE_LINK_CLIENT:
return m_pSysSessionClient;
default:
Assert( !m_pSysSessionClient || !m_pSysSessionHost );
if ( m_pSysSessionHost )
return m_pSysSessionHost;
else if ( m_pSysSessionClient )
return m_pSysSessionClient;
else
return NULL;
}
}
CSysSessionClient * CMatchSessionOnlineTeamSearch::OnBeginJoiningSearchResult()
{
return new CSysTeamSearchClient( m_pSettings, this );
}
void CMatchSessionOnlineTeamSearch::OnSearchCompletedSuccess( CSysSessionClient *pSysSession, KeyValues *pSettings )
{
// Push the settings back into searcher since we are going to be using them
m_pSettings = pSettings;
m_autodelete_pSettings.Assign( m_pSettings );
m_pSysSessionClient = pSysSession;
// Established connection with another team
m_eState = STATE_LINK_CLIENT;
m_xuidLinkPeer = pSysSession->GetHostXuid();
OnSearchEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "searchlinked"
) );
LinkClient().LinkInit();
}
void CMatchSessionOnlineTeamSearch::OnSearchCompletedEmpty( KeyValues *pSettings )
{
// Push the settings back into searcher since we are going to be searching again
m_pSettings = pSettings;
m_autodelete_pSettings.Assign( m_pSettings );
// Idle out for some time
m_eState = STATE_CREATING;
// Notify everybody that our search idled out
OnSearchEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "searchidle"
) );
// Allocate the hosting object to accept matches
m_pSysSessionHost = new CSysTeamSearchHost( m_pSettings, this );
}
void CMatchSessionOnlineTeamSearch::DebugPrint()
{
DevMsg( "CMatchSessionOnlineTeamSearch::CMatchSessionOnlineSearch\n" );
CMatchSessionOnlineSearch::DebugPrint();
DevMsg( "CMatchSessionOnlineTeamSearch [ state=%d ]\n", m_eState );
DevMsg( " linkstate: %d\n", m_iLinkState );
DevMsg( " linkpeer: %llx\n", m_xuidLinkPeer );
DevMsg( " actiontime:%.3f\n", m_flActionTime ? m_flActionTime - Plat_FloatTime() : 0.0f );
if ( m_pDsSearcher )
DevMsg( "TeamSearch: Dedicated search in progress\n" );
else
DevMsg( "TeamSearch: Dedicated search not active\n" );
DevMsg( "TeamSearch: SysSession host state:\n" );
if ( m_pSysSessionHost )
m_pSysSessionHost->DebugPrint();
else
DevMsg( "SysSession is NULL\n" );
DevMsg( "TeamSearch: SysSession client state:\n" );
if ( m_pSysSessionClient )
m_pSysSessionClient->DebugPrint();
else
DevMsg( "SysSession is NULL\n" );
}
void CMatchSessionOnlineTeamSearch::OnEvent( KeyValues *pEvent )
{
CMatchSessionOnlineSearch::OnEvent( pEvent );
// Let the dedicated search handle the responses too
if ( m_pDsSearcher )
m_pDsSearcher->OnEvent( pEvent );
}
void CMatchSessionOnlineTeamSearch::Update()
{
switch ( m_eState )
{
case STATE_ERROR:
if ( m_flActionTime && Plat_FloatTime() > m_flActionTime )
{
m_flActionTime = 0.0f;
if ( m_pHostSession )
m_pHostSession->Command( KeyValues::AutoDeleteInline( new KeyValues( "Stop" ) ) );
}
return;
case STATE_AWAITING_PEER:
break;
case STATE_LINK_CLIENT:
LinkClient().LinkUpdate();
break;
case STATE_LINK_HOST:
LinkHost().LinkUpdate();
break;
}
CMatchSessionOnlineSearch::Update();
if ( CSysSessionBase *pLinkSysSession = LinkSysSession() )
pLinkSysSession->Update();
}
void CMatchSessionOnlineTeamSearch::Destroy()
{
if ( m_pSysSessionHost )
{
m_pSysSessionHost->Destroy();
m_pSysSessionHost = NULL;
}
if ( m_pSysSessionClient )
{
m_pSysSessionClient->Destroy();
m_pSysSessionClient = NULL;
}
if ( m_pDsSearcher )
{
m_pDsSearcher->Destroy();
m_pDsSearcher = NULL;
}
#ifdef _X360
if ( m_pXlspCommandBatch )
{
m_pXlspCommandBatch->Destroy();
m_pXlspCommandBatch = NULL;
}
if ( m_pXlspConnection )
{
m_pXlspConnection->Destroy();
m_pXlspConnection = NULL;
}
#endif
CMatchSessionOnlineSearch::Destroy();
}
//////////////////////////////////////////////////////////////////////////
//
// Link base implementation
//
//
void CMatchSessionOnlineTeamSearchLinkBase::LinkUpdate()
{
switch ( m_iLinkState )
{
case STATE_SEARCHING_DEDICATED:
Assert( m_pDsSearcher );
if ( m_pDsSearcher )
{
m_pDsSearcher->Update();
if ( m_pDsSearcher->IsFinished() )
{
OnDedicatedSearchFinished();
return;
}
}
break;
case STATE_HOSTING_LISTEN_SERVER:
if ( KeyValues *pServer = m_pHostSession->GetSessionSettings()->FindKey( "server" ) )
{
// Listen server has started and we should notify the link peer
if ( KeyValues *pPeerCommand = new KeyValues( "TeamSearchLink::ListenClient" ) )
{
KeyValues::AutoDelete autodelete( pPeerCommand );
pPeerCommand->SetString( "run", "xuid" );
pPeerCommand->SetUint64( "runxuid", m_xuidLinkPeer );
pPeerCommand->AddSubKey( pServer->MakeCopy() );
pPeerCommand->SetString( "server/server", "externalpeer" ); // for other team it is not a listen server, but externalpeer
pPeerCommand->SetInt( "server/team", 2 ); // other team is nominated team 2
LinkSysSession()->Command( pPeerCommand );
}
m_iLinkState = STATE_LINK_FINISHED;
}
break;
}
}
void CMatchSessionOnlineTeamSearchLinkBase::LinkCommand( KeyValues *pCommand )
{
char const *szCommand = pCommand->GetName();
if ( !Q_stricmp( "TeamSearchLink::Dedicated", szCommand ) ||
!Q_stricmp( "TeamSearchLink::ListenClient", szCommand ) )
{
Assert( m_iLinkState == STATE_WAITING_FOR_PEER_SERVER );
KeyValues *pServerInfo = pCommand->FindKey( "server", false );
if ( !pServerInfo )
{
ResetAndRestartTeamSearch();
return;
}
// Apply host session update packet
ApplyHostSessionUpdatePacket();
// Notify our team about the server
if ( KeyValues *pOurTeamNotify = new KeyValues( CFmtStr( "TeamSearchResult::%s", szCommand + strlen( "TeamSearchLink::" ) ) ) )
{
pOurTeamNotify->AddSubKey( pServerInfo->MakeCopy() );
OnSearchEvent( pOurTeamNotify );
}
return;
}
}
void CMatchSessionOnlineTeamSearchLinkBase::StartHostingListenServer()
{
if ( mm_teamsearch_nostart.GetBool() )
{
// Fake-cancel the session for debugging
m_pHostSession->Command( KeyValues::AutoDeleteInline( new KeyValues( "Cancel" ) ) );
return;
}
m_iLinkState = STATE_HOSTING_LISTEN_SERVER;
// Update host session settings packet
ApplyHostSessionUpdatePacket();
OnSearchEvent( new KeyValues( "TeamSearchResult::ListenHost" ) );
}
void CMatchSessionOnlineTeamSearchLinkBase::StartDedicatedServerSearch()
{
if ( mm_teamsearch_nostart.GetBool() )
{
// Fake-cancel the session for debugging
m_pHostSession->Command( KeyValues::AutoDeleteInline( new KeyValues( "Cancel" ) ) );
return;
}
m_iLinkState = STATE_SEARCHING_DEDICATED;
// Notify everybody that we are searching for dedicated server now
OnSearchEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "dedicated"
) );
m_pDsSearcher = new CDsSearcher( m_pSettings, m_pHostSession->GetSessionSystemData()->GetUint64( "xuidReserve" ), NULL );
}
void CMatchSessionOnlineTeamSearchLinkBase::OnDedicatedSearchFinished()
{
Assert( m_pDsSearcher );
CDsSearcher::DsResult_t dsResult = m_pDsSearcher->GetResult();
m_pDsSearcher->Destroy();
m_pDsSearcher = NULL;
if ( !dsResult.m_bDedicated )
{
StartHostingListenServer();
return;
}
//
// Serialize the information about the dedicated server
//
KeyValues *pServerInfo = new KeyValues( "server" );
dsResult.CopyToServerKey( pServerInfo );
pServerInfo->SetUint64( "reservationid", m_pHostSession->GetSessionSystemData()->GetUint64( "xuidReserve" ) );
// Apply host session update packet
ApplyHostSessionUpdatePacket();
//
// Notify the peer team about the server
//
if ( KeyValues *pPeerCommand = new KeyValues( "TeamSearchLink::Dedicated" ) )
{
KeyValues::AutoDelete autodelete( pPeerCommand );
pPeerCommand->SetString( "run", "xuid" );
pPeerCommand->SetUint64( "runxuid", m_xuidLinkPeer );
pServerInfo->SetInt( "team", 2 );
pPeerCommand->AddSubKey( pServerInfo->MakeCopy() );
LinkSysSession()->Command( pPeerCommand );
}
// Notify our team about the server
if ( KeyValues *pOurTeamNotify = new KeyValues( "TeamSearchResult::Dedicated" ) )
{
pOurTeamNotify->SetPtr( "dsresult", &dsResult );
pServerInfo->SetInt( "team", 1 );
pOurTeamNotify->AddSubKey( pServerInfo );
OnSearchEvent( pOurTeamNotify );
}
m_iLinkState = STATE_LINK_FINISHED;
}
void CMatchSessionOnlineTeamSearchLinkBase::StartWaitingForPeerServer()
{
m_iLinkState = STATE_WAITING_FOR_PEER_SERVER;
// Notify everybody that peer is searching for game server
OnSearchEvent( new KeyValues(
"OnMatchSessionUpdate",
"state", "progress",
"progress", "peerserver"
) );
}
//////////////////////////////////////////////////////////////////////////
//
// Link host implementation
//
//
void CMatchSessionOnlineTeamSearchLinkHost::LinkInit()
{
m_iLinkState = STATE_SUBMIT_STATS;
}
void CMatchSessionOnlineTeamSearchLinkHost::LinkUpdate()
{
switch ( m_iLinkState )
{
case STATE_SUBMIT_STATS:
{
#ifdef _X360
m_pXlspConnection = new CXlspConnection( false );
CUtlVector< KeyValues * > arrCommands;
#endif
if ( KeyValues *pCmd = new KeyValues( "stat_agg" ) )
{
int numSeconds = int( 1 + Plat_FloatTime() - m_flCreationTime );
numSeconds = ClampArrayBounds( numSeconds, 30 * 60 ); // 30 minutes
pCmd->SetInt( "search_team_time", numSeconds );
#ifdef _X360
arrCommands.AddToTail( pCmd );
#elif !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR )
CGCClientJobUpdateStats *pJob = new CGCClientJobUpdateStats( pCmd );
pJob->StartJob( NULL );
#else
pCmd->deleteThis();
#endif
}
#ifdef _X360
m_pXlspCommandBatch = new CXlspConnectionCmdBatch( m_pXlspConnection, arrCommands );
#endif
}
m_iLinkState = STATE_REPORTING_STATS;
break;
case STATE_REPORTING_STATS:
#ifdef _X360
m_pXlspCommandBatch->Update();
if ( !m_pXlspCommandBatch->IsFinished() )
return;
m_pXlspCommandBatch->Destroy();
m_pXlspCommandBatch = NULL;
m_pXlspConnection->Destroy();
m_pXlspConnection = NULL;
#endif
m_iLinkState = STATE_CONFIRM_JOIN;
break;
case STATE_CONFIRM_JOIN:
if ( KeyValues *pPeerCommand = new KeyValues( "TeamSearchLink::HostConfirmJoinReady" ) )
{
KeyValues::AutoDelete autodelete( pPeerCommand );
pPeerCommand->SetString( "run", "xuid" );
pPeerCommand->SetUint64( "runxuid", m_xuidLinkPeer );
pPeerCommand->AddSubKey( m_pHostSession->GetSessionSettings()->MakeCopy() ); // Submit a copy of our session settings as well
m_pSysSessionHost->Command( pPeerCommand );
}
m_iLinkState = STATE_CONFIRM_JOIN_WAIT;
break;
case STATE_CONFIRM_JOIN_WAIT:
// Waiting for client to tell us how to select server
break;
}
CMatchSessionOnlineTeamSearchLinkBase::LinkUpdate();
}
void CMatchSessionOnlineTeamSearchLinkHost::LinkCommand( KeyValues *pCommand )
{
char const *szCommand = pCommand->GetName();
if ( !Q_stricmp( "TeamSearchLink::TeamLinkSessionUpdate", szCommand ) )
{
if ( KeyValues *pUpdatePkt = pCommand->GetFirstTrueSubKey() )
{
m_pSettings->MergeFrom( pUpdatePkt );
RememberHostSessionUpdatePacket( pUpdatePkt->MakeCopy() );
}
}
else if ( !Q_stricmp( "TeamSearchLink::HostHosting", szCommand ) )
{
// We are going to host
char const *szLinkHostServer = m_pSettings->GetString( "options/server", "" );
if ( !Q_stricmp( szLinkHostServer, "listen" ) )
StartHostingListenServer();
else
StartDedicatedServerSearch();
return;
}
else if ( !Q_stricmp( "TeamSearchLink::ClientHosting", szCommand ) )
{
// Our peer is going to host
StartWaitingForPeerServer();
return;
}
CMatchSessionOnlineTeamSearchLinkBase::LinkCommand( pCommand );
}
//////////////////////////////////////////////////////////////////////////
//
// Link client implementation
//
//
void CMatchSessionOnlineTeamSearchLinkClient::LinkInit()
{
m_iLinkState = STATE_WAITING_FOR_HOST_READY;
}
void CMatchSessionOnlineTeamSearchLinkClient::LinkUpdate()
{
switch ( m_iLinkState )
{
case STATE_WAITING_FOR_HOST_READY:
// We are waiting for host to report statistics and get the merged teamlink lobby ready
return;
case STATE_CONFIRM_JOIN:
// m_pSettings has been set to the aggregate of host settings
// and our members, so we can inspect host's preference of the Server to
// decide who is going to search for the game server
{
char const *szLinkHostServer = m_pSettings->GetString( "options/server", "" );
char const *szOurServer = m_pHostSession->GetSessionSettings()->GetString( "options/server", "" );
if ( Q_stricmp( szLinkHostServer, "listen" ) &&
!Q_stricmp( szOurServer, "listen" ) )
{
// Host doesn't care, but we need listen server
m_pSysSessionClient->Command( KeyValues::AutoDeleteInline( new KeyValues(
"TeamSearchLink::ClientHosting", "run", "host" ) ) );
StartHostingListenServer();
}
else
{
// Let host find the server
m_pSysSessionClient->Command( KeyValues::AutoDeleteInline( new KeyValues(
"TeamSearchLink::HostHosting", "run", "host" ) ) );
StartWaitingForPeerServer();
}
}
return;
}
CMatchSessionOnlineTeamSearchLinkBase::LinkUpdate();
}
void CMatchSessionOnlineTeamSearchLinkClient::LinkCommand( KeyValues *pCommand )
{
char const *szCommand = pCommand->GetName();
if ( !Q_stricmp( "TeamSearchLink::HostConfirmJoinReady", szCommand ) )
{
// Host has sent us full settings of the host session,
// let's try to reconcile and summarize the settings and prepare an update package if needed
KeyValues *pRemoteSettings = pCommand->GetFirstTrueSubKey();
KeyValues *pLocalSettings = m_pHostSession->GetSessionSettings();
if ( KeyValues *pUpdatePkt = g_pMMF->GetMatchTitleGameSettingsMgr()->PrepareTeamLinkForGame( pLocalSettings, pRemoteSettings ) )
{
if ( KeyValues *pHostCmd = new KeyValues( "TeamSearchLink::TeamLinkSessionUpdate", "run", "host" ) )
{
pHostCmd->AddSubKey( pUpdatePkt->MakeCopy() );
m_pSysSessionClient->Command( KeyValues::AutoDeleteInline( pHostCmd ) );
}
m_pSettings->MergeFrom( pUpdatePkt );
RememberHostSessionUpdatePacket( pUpdatePkt );
}
// Host is now ready
if ( m_iLinkState == STATE_WAITING_FOR_HOST_READY )
m_iLinkState = STATE_CONFIRM_JOIN;
return;
}
CMatchSessionOnlineTeamSearchLinkBase::LinkCommand( pCommand );
}

View File

@@ -0,0 +1,181 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_SESSION_ONLINE_TEAM_SEARCH_H
#define MM_SESSION_ONLINE_TEAM_SEARCH_H
#ifdef _WIN32
#pragma once
#endif
class CMatchSessionOnlineTeamSearch;
class CMatchSessionOnlineTeamSearchLinkHost;
class CMatchSessionOnlineTeamSearchLinkClient;
//
// CMatchSessionOnlineTeamSearch
//
// Implementation of an online team session search (aka team-on-team matchmaking)
//
class CMatchSessionOnlineTeamSearch : public CMatchSessionOnlineSearch
{
public:
explicit CMatchSessionOnlineTeamSearch( KeyValues *pSettings, CMatchSessionOnlineHost *pHost );
virtual ~CMatchSessionOnlineTeamSearch();
//
// Overrides when search is used as a nested object
//
protected:
virtual CMatchSearcher * OnStartSearching();
virtual void OnSearchCompletedSuccess( CSysSessionClient *pSysSession, KeyValues *pSettings );
virtual void OnSearchCompletedEmpty( KeyValues *pSettings );
virtual void OnSearchEvent( KeyValues *pNotify );
virtual CSysSessionClient * OnBeginJoiningSearchResult();
// Hooks for the nested sys sessions
public:
virtual void OnSessionEvent( KeyValues *pNotify );
CSysSessionBase * LinkSysSession();
//
// Match session overrides
//
public:
// Run a frame update
virtual void Update();
// Destroy the session object
virtual void Destroy();
// Debug print a session object
virtual void DebugPrint();
// Process event
virtual void OnEvent( KeyValues *pEvent );
protected:
void OnRunSessionCommand( KeyValues *pCommand );
CMatchSessionOnlineTeamSearchLinkHost & LinkHost();
CMatchSessionOnlineTeamSearchLinkClient & LinkClient();
void ResetAndRestartTeamSearch();
void RememberHostSessionUpdatePacket( KeyValues *pPacket );
void ApplyHostSessionUpdatePacket();
protected:
enum State_t
{
STATE_SEARCHING,
STATE_CREATING,
STATE_AWAITING_PEER,
STATE_LINK_HOST,
STATE_LINK_CLIENT,
STATE_ERROR,
};
State_t m_eState;
int m_iLinkState;
XUID m_xuidLinkPeer;
CMatchSessionOnlineHost *m_pHostSession; // parent object that contains our TeamSearch
CSysSessionHost *m_pSysSessionHost; // nested SysSessionHost object when we are hosting
CSysSessionClient *m_pSysSessionClient; // nested SysSessionClient object when we are peer
CDsSearcher *m_pDsSearcher; // for searching dedicated servers
float m_flActionTime; // time to perform action of the current state
KeyValues *m_pUpdateHostSessionPacket; // packet that should be applied to host session before the game commences
// this packet is the case of team sessions being reconciled and unknown settings resolved
KeyValues::AutoDelete m_autodelete_pUpdateHostSessionPacket;
// LINK HOST STATE
float m_flCreationTime;
#ifdef _X360
CXlspConnection *m_pXlspConnection;
CXlspConnectionCmdBatch *m_pXlspCommandBatch;
#endif
};
class CMatchSessionOnlineTeamSearchLinkBase : public CMatchSessionOnlineTeamSearch
{
private:
CMatchSessionOnlineTeamSearchLinkBase();
public:
enum State_t
{
STATE_HOSTING_LISTEN_SERVER,
STATE_WAITING_FOR_PEER_SERVER,
STATE_SEARCHING_DEDICATED,
STATE_LINK_FINISHED,
STATE_LINK_BASE_LAST
};
protected:
void StartHostingListenServer();
void StartDedicatedServerSearch();
void StartWaitingForPeerServer();
void OnDedicatedSearchFinished();
public:
void LinkCommand( KeyValues *pCommand );
void LinkUpdate();
};
class CMatchSessionOnlineTeamSearchLinkHost : public CMatchSessionOnlineTeamSearchLinkBase
{
private:
CMatchSessionOnlineTeamSearchLinkHost();
public:
enum State_t
{
STATE_LINK_INITIAL = STATE_LINK_BASE_LAST,
STATE_SUBMIT_STATS,
STATE_REPORTING_STATS,
STATE_CONFIRM_JOIN,
STATE_CONFIRM_JOIN_WAIT,
};
public:
void LinkInit();
void LinkCommand( KeyValues *pCommand );
void LinkUpdate();
};
class CMatchSessionOnlineTeamSearchLinkClient : public CMatchSessionOnlineTeamSearchLinkBase
{
private:
CMatchSessionOnlineTeamSearchLinkClient();
public:
enum State_t
{
STATE_LINK_INITIAL = STATE_LINK_BASE_LAST,
STATE_WAITING_FOR_HOST_READY,
STATE_CONFIRM_JOIN,
};
public:
void LinkInit();
void LinkCommand( KeyValues *pCommand );
void LinkUpdate();
};
class CMatchSearcher_OnlineTeamSearch : public CMatchSearcher_OnlineSearch
{
public:
CMatchSearcher_OnlineTeamSearch( CMatchSessionOnlineTeamSearch *pSession, KeyValues *pSettings );
protected:
virtual void StartSearchPass( KeyValues *pSearchPass );
};
#endif

View File

@@ -0,0 +1,38 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_TITLE_MAIN_H
#define MM_TITLE_MAIN_H
#ifdef _WIN32
#pragma once
#endif
extern InitReturnVal_t MM_Title_Init();
extern void MM_Title_Shutdown();
extern IMatchTitle *g_pIMatchTitle;
extern IMatchEventsSink *g_pIMatchTitleEventsSink;
extern IMatchTitleGameSettingsMgr *g_pIMatchTitleGameSettingsMgr;
//
// LINK_MATCHMAKING_LIB() macro must be included in the matchmaking.dll code
// to force all required matchmaking objects linked into the DLL.
//
extern void LinkMatchmakingLib();
#define LINK_MATCHMAKING_LIB() \
namespace { \
static class CLinkMatchmakingLib { \
public: \
CLinkMatchmakingLib() { \
LinkMatchmakingLib(); \
} \
} s_LinkHelper; \
};
#endif // MM_EVENTS_H

563
matchmaking/mm_voice.cpp Normal file
View File

@@ -0,0 +1,563 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_voice.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#if defined( _GAMECONSOLE ) && !defined( _CERT )
ConVar mm_voice_fulldebug( "mm_voice_fulldebug", "0", FCVAR_DEVELOPMENTONLY );
#define MMVOICEMSG(...) if ( mm_voice_fulldebug.GetInt() > 0 ) { Msg( "[MMVOICE] " __VA_ARGS__ ); }
#define MMVOICEMSG2(...) if ( mm_voice_fulldebug.GetInt() > 1 ) { Msg( "[MMVOICE] " __VA_ARGS__ ); }
#else
#define MMVOICEMSG(...) ((void)0)
#define MMVOICEMSG2(...) ((void)0)
#endif
#if !defined(NO_STEAM) && !defined( SWDS )
static inline bool FriendRelationshipMute( int iRelationship )
{
switch ( iRelationship )
{
case k_EFriendRelationshipBlocked:
case k_EFriendFlagIgnored:
case k_EFriendFlagIgnoredFriend:
return true;
default:
return false;
}
}
#endif
//
// Construction/destruction
//
CMatchVoice::CMatchVoice()
{
;
}
CMatchVoice::~CMatchVoice()
{
;
}
static CMatchVoice g_MatchVoice;
CMatchVoice *g_pMatchVoice = &g_MatchVoice;
//
// Implementation
//
// Whether remote player talking can be visualized / audible
bool CMatchVoice::CanPlaybackTalker( XUID xuidTalker )
{
if ( IsMachineMutingLocalTalkers( xuidTalker ) )
{
MMVOICEMSG2( "CanPlaybackTalker(0x%llX)=false(IsMachineMutingLocalTalkers)\n", xuidTalker );
return false;
}
if ( IsMachineMuted( xuidTalker ) )
{
MMVOICEMSG2( "CanPlaybackTalker(0x%llX)=false(IsMachineMuted)\n", xuidTalker );
return false;
}
return true;
}
// Whether we are explicitly muting a remote player
bool CMatchVoice::IsTalkerMuted( XUID xuidTalker )
{
#if defined( _PS3 ) && !defined( NO_STEAM )
if ( steamapicontext->SteamFriends()->GetUserRestrictions() )
{
MMVOICEMSG( "IsTalkerMuted(0x%llX)=true(GetUserRestrictions)\n", xuidTalker );
return true;
}
#endif
#if !defined(NO_STEAM) && !defined( SWDS )
if ( FriendRelationshipMute( steamapicontext->SteamFriends()->GetFriendRelationship( xuidTalker ) ) )
{
MMVOICEMSG( "IsTalkerMuted(0x%llX)=true(GetFriendRelationship=0x%X)\n", xuidTalker, steamapicontext->SteamFriends()->GetFriendRelationship( xuidTalker ) );
return true;
}
if ( m_arrMutedTalkers.Find( xuidTalker ) != m_arrMutedTalkers.InvalidIndex() )
{
MMVOICEMSG( "IsTalkerMuted(0x%llX)=true(locallist)\n", xuidTalker );
return true;
}
#endif
#if defined( _GAMECONSOLE ) && !defined( _CERT )
XUID xuidOriginal = xuidTalker; xuidOriginal;
#endif
xuidTalker = RemapTalkerXuid( xuidTalker );
#if !defined(NO_STEAM) && !defined( SWDS )
if ( FriendRelationshipMute( steamapicontext->SteamFriends()->GetFriendRelationship( xuidTalker ) ) )
{
MMVOICEMSG( "IsTalkerMuted(0x%llX/0x%llX)=true(GetFriendRelationship=0x%X)\n", xuidTalker, xuidOriginal, steamapicontext->SteamFriends()->GetFriendRelationship( xuidTalker ) );
return true;
}
#endif
#ifdef _X360
if ( MMX360_GetUserCtrlrIndex( xuidTalker ) >= 0 )
// local players are never considered muted locally
return false;
for ( DWORD dwCtrlr = 0; dwCtrlr < XUSER_MAX_COUNT; ++ dwCtrlr )
{
int iSlot = ( XBX_GetNumGameUsers() > 0 ) ? XBX_GetSlotByUserId( dwCtrlr ) : -1;
if ( iSlot >= 0 && iSlot < ( int ) XBX_GetNumGameUsers() &&
XBX_GetUserIsGuest( iSlot ) )
continue;
BOOL mutedInGuide = false;
if ( ERROR_SUCCESS == g_pMatchExtensions->GetIXOnline()->XUserMuteListQuery( dwCtrlr, xuidTalker, &mutedInGuide ) &&
mutedInGuide )
{
return true;
}
}
#endif
if ( m_arrMutedTalkers.Find( xuidTalker ) != m_arrMutedTalkers.InvalidIndex() )
{
MMVOICEMSG( "IsTalkerMuted(0x%llX/0x%llX)=true(locallist)\n", xuidTalker, xuidOriginal );
return true;
}
return false;
}
// Whether we are muting any player on the player's machine
bool CMatchVoice::IsMachineMuted( XUID xuidPlayer )
{
#ifdef _X360
if ( MMX360_GetUserCtrlrIndex( xuidPlayer ) >= 0 )
// local players are never considered muted locally
return false;
// Find the session and the talker within session members
IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession();
if ( !pMatchSession )
return IsTalkerMutedWithPrivileges( -1, xuidPlayer );
KeyValues *pSettings = pMatchSession->GetSessionSettings();
KeyValues *pMachine = NULL;
KeyValues *pTalker = SessionMembersFindPlayer( pSettings, xuidPlayer, &pMachine );
if ( !pTalker || !pMachine )
return IsTalkerMutedWithPrivileges( -1, xuidPlayer );
// Walk all users from that machine
int numPlayers = pMachine->GetInt( "numPlayers" );
for ( int k = 0; k < numPlayers; ++ k )
{
KeyValues *pOtherPlayer = pMachine->FindKey( CFmtStr( "player%d", k ) );
if ( !pOtherPlayer )
continue;
char const *szOtherName = pOtherPlayer->GetString( "name" );
if ( strchr( szOtherName, '(' ) )
continue;
XUID xuidOther = pOtherPlayer->GetUint64( "xuid" );
if ( IsTalkerMutedWithPrivileges( -1, xuidOther ) )
return true;
}
return false;
#else
return IsTalkerMuted( xuidPlayer );
#endif
}
#ifdef _PS3
struct TalkerXuidRemap_t
{
XUID xuidSteamId;
XUID xuidPsnId;
};
#define TALKER_REMAP_CACHE_SIZE 4
static CUtlVector< TalkerXuidRemap_t > g_arrTalkerRemapCache( 0, TALKER_REMAP_CACHE_SIZE );
#endif
// X360: Remap XUID of a player to a valid LIVE-enabled XUID
// PS3: Remap SteamID of a player to a PSN ID
XUID CMatchVoice::RemapTalkerXuid( XUID xuidTalker )
{
if ( !IsGameConsole() )
return xuidTalker;
#ifdef _PS3
for ( int k = 0; k < g_arrTalkerRemapCache.Count(); ++ k )
if ( g_arrTalkerRemapCache[k].xuidSteamId == xuidTalker )
return g_arrTalkerRemapCache[k].xuidPsnId;
#endif
// Find the session and the talker within session members
IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession();
if ( !pMatchSession )
return xuidTalker;
KeyValues *pSettings = pMatchSession->GetSessionSettings();
KeyValues *pMachine = NULL;
KeyValues *pTalker = SessionMembersFindPlayer( pSettings, xuidTalker, &pMachine );
if ( !pTalker || !pMachine )
return xuidTalker;
#ifdef _PS3
XUID xuidPsnId = pMachine->GetUint64( "psnid" );
if ( !xuidPsnId )
return xuidTalker;
if ( g_arrTalkerRemapCache.Count() >= TALKER_REMAP_CACHE_SIZE )
g_arrTalkerRemapCache.SetCountNonDestructively( TALKER_REMAP_CACHE_SIZE - 1 );
TalkerXuidRemap_t txr = { xuidTalker, xuidPsnId };
g_arrTalkerRemapCache.AddToHead( txr );
return xuidPsnId;
#endif
// Check this user name if he is a guest
char const *szTalkerName = pTalker->GetString( "name" );
char const *pchr = strchr( szTalkerName, '(' );
if ( !pchr )
return xuidTalker; // user is not a guest
// Find another user from the same machine
int numPlayers = pMachine->GetInt( "numPlayers" );
for ( int k = 0; k < numPlayers; ++ k )
{
KeyValues *pOtherPlayer = pMachine->FindKey( CFmtStr( "player%d", k ) );
if ( !pOtherPlayer )
continue;
char const *szOtherName = pOtherPlayer->GetString( "name" );
if ( strchr( szOtherName, '(' ) )
continue;
XUID xuidOther = pOtherPlayer->GetUint64( "xuid" );
if ( xuidOther )
return xuidOther;
}
// No remapping
return xuidTalker;
}
// Check player-player voice privileges for machine blocking purposes
bool CMatchVoice::IsTalkerMutedWithPrivileges( int dwCtrlr, XUID xuidTalker )
{
#ifdef _X360
if ( -1 == dwCtrlr ) // all controllers should be considered
{
for ( dwCtrlr = 0; dwCtrlr < XUSER_MAX_COUNT; ++ dwCtrlr )
{
if ( IsTalkerMutedWithPrivileges( dwCtrlr, xuidTalker ) )
return true;
}
return false;
}
// Analyze this particular local controller against the given talker
int iSlot = ( XBX_GetNumGameUsers() > 0 ) ? XBX_GetSlotByUserId( dwCtrlr ) : -1;
if ( iSlot >= 0 && iSlot < ( int ) XBX_GetNumGameUsers() &&
XBX_GetUserIsGuest( iSlot ) )
// Guest has no say
return false;
XUSER_SIGNIN_INFO xsi;
if ( ERROR_SUCCESS == XUserGetSigninInfo( dwCtrlr, XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi ) )
{
if ( xsi.dwInfoFlags & XUSER_INFO_FLAG_GUEST )
// LIVE guests have no say
return false;
}
BOOL mutedInGuide = false;
if ( ERROR_SUCCESS == g_pMatchExtensions->GetIXOnline()->XUserMuteListQuery( dwCtrlr, xuidTalker, &mutedInGuide ) &&
mutedInGuide )
{
return true;
}
// Check permissions to see if this player has friends-only or no communication set
// Don't check permissions against other local players
// Check for open privileges
BOOL bHasPrivileges;
DWORD dwResult = XUserCheckPrivilege( dwCtrlr, XPRIVILEGE_COMMUNICATIONS, &bHasPrivileges );
if ( dwResult == ERROR_SUCCESS )
{
if ( !bHasPrivileges )
{
// Second call checks for friends-only
XUserCheckPrivilege( dwCtrlr, XPRIVILEGE_COMMUNICATIONS_FRIENDS_ONLY, &bHasPrivileges );
if ( bHasPrivileges )
{
// Privileges are set to friends-only. See if the remote player is on our friends list.
BOOL bIsFriend;
dwResult = XUserAreUsersFriends( dwCtrlr, &xuidTalker, 1, &bIsFriend, NULL );
if ( dwResult != ERROR_SUCCESS || !bIsFriend )
{
return true;
}
}
else
{
// Privilege is nobody, mute them all
return true;
}
}
}
#endif
if ( m_arrMutedTalkers.Find( xuidTalker ) != m_arrMutedTalkers.InvalidIndex() )
{
MMVOICEMSG( "IsTalkerMutedWithPrivileges(%d/0x%llX)=true(locallist)\n", dwCtrlr, xuidTalker );
return true;
}
return false;
}
// Check if player machine is muting any of local players
bool CMatchVoice::IsMachineMutingLocalTalkers( XUID xuidPlayer )
{
// Find the session and the talker within session members
IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession();
if ( !pMatchSession )
return false;
KeyValues *pSettings = pMatchSession->GetSessionSettings();
KeyValues *pMachine = NULL;
SessionMembersFindPlayer( pSettings, xuidPlayer, &pMachine );
if ( !pMachine )
return false;
// Find the local player record in the session
XUID xuidLocalId = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
KeyValues *pLocalMachine = NULL;
SessionMembersFindPlayer( pSettings, xuidLocalId, &pLocalMachine );
if ( !pLocalMachine || pLocalMachine == pMachine )
return false;
int numLocalPlayers = pLocalMachine->GetInt( "numPlayers" );
// Check the mutelist on the machine
if ( KeyValues *pMutelist = pMachine->FindKey( "Mutelist" ) )
{
for ( KeyValues *val = pMutelist->GetFirstValue(); val; val = val->GetNextValue() )
{
XUID xuidMuted = val->GetUint64();
if ( !xuidMuted )
continue;
for ( int iLocal = 0; iLocal < numLocalPlayers; ++ iLocal )
{
XUID xuidLocal = pLocalMachine->GetUint64( CFmtStr( "player%d/xuid", iLocal ) );
if ( xuidMuted == xuidLocal )
{
MMVOICEMSG2( "IsMachineMutingLocalTalkers(0x%llX/0x%llX)=true(mutelist)\n", xuidPlayer, xuidLocal );
return true;
}
}
}
}
return false;
}
// Whether voice recording mode is currently active
bool CMatchVoice::IsVoiceRecording()
{
#if !defined(_X360) && !defined(NO_STEAM) && !defined( SWDS )
#ifdef _PS3
EVoiceResult res = steamapicontext->SteamUser()->GetAvailableVoice( NULL, NULL, 11025 );
#else
EVoiceResult res = steamapicontext->SteamUser()->GetAvailableVoice( NULL, NULL, 0 );
#endif
switch ( res )
{
case k_EVoiceResultOK:
case k_EVoiceResultNoData:
return true;
default:
return false;
}
#endif
return false;
}
// Enable or disable voice recording
void CMatchVoice::SetVoiceRecording( bool bRecordingEnabled )
{
#if !defined(_X360) && !defined(NO_STEAM) && !defined( SWDS )
if ( bRecordingEnabled )
steamapicontext->SteamUser()->StartVoiceRecording();
else
steamapicontext->SteamUser()->StopVoiceRecording();
#endif
}
// Enable or disable voice mute for a given talker
void CMatchVoice::MuteTalker( XUID xuidTalker, bool bMute )
{
#if !defined(_X360) && !defined(NO_STEAM) && !defined( SWDS )
if ( !xuidTalker )
{
if ( !bMute )
m_arrMutedTalkers.Purge();
}
else
{
m_arrMutedTalkers.FindAndFastRemove( xuidTalker );
if ( bMute )
{
m_arrMutedTalkers.AddToTail( xuidTalker );
}
}
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnSysMuteListChanged" ) );
#endif
}
CON_COMMAND( voice_reset_mutelist, "Reset all mute information for all players who were ever muted." )
{
g_pMatchVoice->MuteTalker( 0ull, false );
Msg( "Mute list cleared.\n" );
}
#if !defined( _X360 ) && !defined( NO_STEAM )
CON_COMMAND( voice_mute, "Mute a specific Steam user" )
{
if ( args.ArgC() != 2 )
{
goto usage;
}
else
{
int iUserId = Q_atoi( args.Arg( 1 ) );
player_info_t pi;
if ( !g_pMatchExtensions->GetIVEngineClient()->GetPlayerInfo( iUserId, &pi ) || !pi.xuid )
{
Msg( "Player# is invalid or refers to a bot, please use \"voice_show_mute\" command.\n" );
goto usage;
}
g_pMatchVoice->MuteTalker( pi.xuid, true );
if ( !g_pMatchExtensions->GetIVEngineClient()->GetDemoPlaybackParameters() )
{
Msg( "%s is now muted.\n", pi.name );
}
return;
}
usage:
Msg( "Example usage: voice_mute player# - where player# is a number that you can find with \"voice_show_mute\" command.\n" );
}
CON_COMMAND( voice_unmute, "Unmute a specific Steam user, or `all` to unmute all connected players." )
{
if ( args.ArgC() != 2 )
{
goto usage;
}
else
{
if ( !Q_stricmp( "all", args.Arg(1) ) )
{
XUID xuidLocal = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
int maxClients = g_pMatchExtensions->GetIVEngineClient()->GetMaxClients();
for ( int i = 1; i <= maxClients; ++ i )
{
// Get the player info from the engine
player_info_t pi;
if ( !g_pMatchExtensions->GetIVEngineClient()->GetPlayerInfo( i, &pi ) )
continue;
if ( !pi.xuid )
continue;
if ( pi.xuid == xuidLocal )
continue;
g_pMatchVoice->MuteTalker( pi.xuid, false );
}
Msg( "All connected players have been unmuted.\n" );
return;
}
int iUserId = Q_atoi( args.Arg( 1 ) );
player_info_t pi;
if ( !g_pMatchExtensions->GetIVEngineClient()->GetPlayerInfo( iUserId, &pi ) || !pi.xuid )
{
Msg( "Player# is invalid or refers to a bot, please use \"voice_show_mute\" command.\n" );
goto usage;
}
g_pMatchVoice->MuteTalker( pi.xuid, false );
if ( !g_pMatchExtensions->GetIVEngineClient()->GetDemoPlaybackParameters() )
{
Msg( "%s is now unmuted.\n", pi.name );
}
return;
}
usage:
Msg( "Example usage: voice_unmute {player#|all} - where player# is a number that you can find with \"voice_show_mute\" command, or all to unmute all connected players.\n" );
}
CON_COMMAND( voice_show_mute, "Show whether current players are muted." )
{
if ( g_pMatchExtensions->GetIVEngineClient()->GetDemoPlaybackParameters() )
return;
bool bPrinted = false;
XUID xuidLocal = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
int maxClients = g_pMatchExtensions->GetIVEngineClient()->GetMaxClients();
for ( int i = 1; i <= maxClients; ++ i )
{
// Get the player info from the engine
player_info_t pi;
if ( !g_pMatchExtensions->GetIVEngineClient()->GetPlayerInfo( i, &pi ) )
continue;
if ( !pi.xuid )
continue;
if ( pi.xuid == xuidLocal )
continue;
if ( !bPrinted )
{
bPrinted = true;
Msg( "Player# Player Name\n" );
Msg( "------- ----------------\n" );
}
Msg( " % 2d %s %s\n", i, g_pMatchVoice->IsTalkerMuted( pi.xuid ) ? "(muted)" : " ", pi.name );
}
if ( bPrinted )
{
Msg( "------- ----------------\n" );
}
else
{
Msg( "No players currently connected who can be muted.\n" );
}
}
#endif

59
matchmaking/mm_voice.h Normal file
View File

@@ -0,0 +1,59 @@
//===== Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MM_VOICE_H
#define MM_VOICE_H
#ifdef _WIN32
#pragma once
#endif
#include "mm_framework.h"
#include "utlvector.h"
class CMatchVoice : public IMatchVoice
{
// Methods of IMatchVoice
public:
// Whether remote player talking can be visualized / audible
virtual bool CanPlaybackTalker( XUID xuidTalker );
// Whether we are explicitly muting a remote player
virtual bool IsTalkerMuted( XUID xuidTalker );
// Whether we are muting any player on the player's machine
virtual bool IsMachineMuted( XUID xuidPlayer );
// Whether voice recording mode is currently active
virtual bool IsVoiceRecording();
// Enable or disable voice recording
virtual void SetVoiceRecording( bool bRecordingEnabled );
// Enable or disable voice mute for a given talker
virtual void MuteTalker( XUID xuidTalker, bool bMute );
protected:
// Remap XUID of a player to a valid LIVE-enabled XUID
XUID RemapTalkerXuid( XUID xuidTalker );
// Check player-player voice privileges for machine blocking purposes
bool IsTalkerMutedWithPrivileges( int iCtrlr, XUID xuidTalker );
// Check if player machine is muting any of local players
bool IsMachineMutingLocalTalkers( XUID xuidPlayer );
public:
CMatchVoice();
~CMatchVoice();
protected:
CUtlVector< XUID > m_arrMutedTalkers;
};
// Match events subscription singleton
extern CMatchVoice *g_pMatchVoice;
#endif // MM_VOICE_H

2992
matchmaking/player.cpp Normal file

File diff suppressed because it is too large Load Diff

306
matchmaking/player.h Normal file
View File

@@ -0,0 +1,306 @@
//========= Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef _PLAYER_H_
#define _PLAYER_H_
class PlayerFriend;
class PlayerLocal;
#include "mm_framework.h"
#include "matchmaking/iplayer.h"
#include "matchmaking/cstrike15/imatchext_cstrike15.h"
#include "playerrankingdata.h"
#include "mathlib/expressioncalculator.h"
//Players can be created several ways
// -They are the local player and this is a reference to themselves
// -They are created by the friends enumerator
// -They are part of a party that a player joins
// -They are part of a game that a player joins.
template < typename TBaseInterface >
class Player : public TBaseInterface
{
protected:
Player() : m_xuid( 0 ), m_iController( XUSER_INDEX_NONE ), m_eOnlineState( IPlayer::STATE_OFFLINE )
{
memset( m_szName, 0, sizeof( m_szName ) );
strcpy(m_szName, "EMPTY");
}
virtual ~Player() {}
public:
// Updates connection info if needed.
virtual void Update() = 0;
// Deletes the player class instance
virtual void Destroy() = 0;
//
// IPlayer implementation
//
public:
virtual XUID GetXUID() { return m_xuid; }
virtual int GetPlayerIndex() { return m_iController; }
virtual const char * GetName() { return m_szName; }
virtual IPlayer::OnlineState_t GetOnlineState() { return m_eOnlineState; }
protected:
XUID m_xuid;
int m_iController;
IPlayer::OnlineState_t m_eOnlineState;
char m_szName[MAX_PLAYER_NAME_LENGTH];
};
class PlayerFriend : public Player< IPlayerFriend >
{
public:
struct FriendInfo_t
{
char const *m_szName;
wchar_t const *m_wszRichPresence;
XNKID m_xSessionID;
uint64 m_uiTitleID;
uint32 m_uiGameServerIP;
KeyValues *m_pGameDetails;
};
public:
explicit PlayerFriend( XUID xuid, FriendInfo_t const *pFriendInfo = NULL );
//
// IPlayerFriend implementation
//
public:
virtual wchar_t const * GetRichPresence();
virtual KeyValues *GetGameDetails();
virtual KeyValues *GetPublishedPresence();
virtual bool IsJoinable();
virtual void Join();
virtual uint64 GetTitleID();
virtual uint32 GetGameServerIP();
//
// Player<IPlayerFriend> implementation
//
public:
virtual void Update();
virtual void Destroy();
public:
void SetFriendMark( unsigned maskSetting );
unsigned GetFriendMark( );
void SetIsStale( bool bStale );
bool GetIsStale();
void UpdateFriendInfo( FriendInfo_t const *pFriendInfo );
bool IsUpdatingInfo();
public:
void StartSearchForSessionInfo();
void StartSearchForSessionInfoImpl();
void AbortSearch();
protected:
unsigned m_uFriendMark;
bool m_bIsStale;
wchar_t m_wszRichPresence[MAX_RICHPRESENCE_SIZE];
uint64 m_uiTitleID;
uint32 m_uiGameServerIP;
XNKID m_xSessionID;
XSESSION_INFO m_GameSessionInfo;
KeyValues *m_pDetails;
KeyValues *m_pPublishedPresence;
enum SearchState_t
{
SEARCH_NONE,
SEARCH_QUEUED,
#ifdef _X360
SEARCH_XNKID,
SEARCH_QOS,
#elif !defined( NO_STEAM )
SEARCH_WAIT_LOBBY_DATA,
#endif
SEARCH_COMPLETED
};
SearchState_t m_eSearchState;
#ifdef _X360
XSESSION_INFO m_xsiSearchState;
XNADDR const *m_pQOS_xnaddr;
XNKID const *m_pQOS_xnkid;
XNKEY const *m_pQOS_xnkey;
XNQOS *m_XNQOS;
XOVERLAPPED m_SessionSearchOverlapped;
CUtlBuffer m_bufSessionSearchResults;
XSESSION_SEARCHRESULT_HEADER * GetXSearchResults() { return ( XSESSION_SEARCHRESULT_HEADER * ) m_bufSessionSearchResults.Base(); }
void Live_Update_SearchXNKID();
void Live_Update_Search_QOS();
#elif !defined( NO_STEAM )
STEAM_CALLBACK_MANUAL( PlayerFriend, Steam_OnLobbyDataUpdate, LobbyDataUpdate_t, m_CallbackOnLobbyDataUpdate );
#endif
};
class PlayerLocal : public Player< IPlayerLocal >
{
public:
explicit PlayerLocal( int iController );
~PlayerLocal();
public:
void RecomputeXUID( char const *szNetwork );
void DetectOnlineState();
//
// IPlayerLocal implementation
//
public:
virtual const UserProfileData& GetPlayerProfileData();
virtual MatchmakingData * GetPlayerMatchmakingData( void );
virtual void UpdatePlayerMatchmakingData( int mmDataType );
virtual void ResetPlayerMatchmakingData( int mmDataScope );
virtual const void * GetPlayerTitleData( int iTitleDataIndex );
virtual void UpdatePlayerTitleData( TitleDataFieldsDescription_t const *fdKey, const void *pvNewTitleData, int numNewBytes );
virtual void GetLeaderboardData( KeyValues *pLeaderboardInfo );
virtual void UpdateLeaderboardData( KeyValues *pLeaderboardInfo );
virtual void GetAwardsData( KeyValues *pAwardsData );
virtual void UpdateAwardsData( KeyValues *pAwardsData );
virtual void SetNeedsSave( void );
#if defined ( _X360 )
virtual bool IsTitleDataValid() { return m_bIsTitleDataValid; };
virtual bool IsTitleDataBlockValid( int blockId );
virtual void SetIsTitleDataValid( bool isValid ) { m_bIsTitleDataValid = isValid; }
virtual bool IsFreshPlayerProfile( void ) { return m_bIsFreshPlayerProfile; }
virtual void ClearBufTitleData( void );
#endif
//
// Player<IPlayerLocal> implementation
//
public:
virtual void Update();
virtual void Destroy();
public:
XUSER_SIGNIN_STATE GetAssumedSigninState();
void LoadPlayerProfileData();
void LoadTitleData();
void SetTitleDataWriteTime( float flTime );
void WriteTitleData();
bool IsTitleDataStorageConnected( void );
void OnLeaderboardRequestFinished( KeyValues *pLeaderboardData );
void SetFlag_AwaitingTitleData() { m_uiPlayerFlags |= PLAYER_INVITE_AWAITING_TITLEDATA; }
#if defined ( _X360 )
bool m_bIsTitleDataValid;
bool m_bIsFreshPlayerProfile;
#endif
protected:
//stats
XUSER_SIGNIN_STATE m_eLoadedTitleData;
float m_flLastSave;
enum Flags_t
{
PLAYER_INVITE_AWAITING_TITLEDATA = ( 1 << 0 )
};
uint32 m_uiPlayerFlags;
enum Consts_t
{
TITLE_DATA_COUNT_X360 = 3, // number of Xbox LIVE - backed title data buffers
TITLE_DATA_COUNT = 3, // number of valid title data buffers
};
UserProfileData m_ProfileData;
MatchmakingData m_MatchmakingData;
CUtlVector<int> m_arrAchievementsEarned, m_arrAvatarAwardsEarned;
char m_bufTitleData[TITLE_DATA_COUNT][XPROFILE_SETTING_MAX_SIZE];
bool m_bSaveTitleData[TITLE_DATA_COUNT];
#if defined ( _X360 )
bool m_bIsTitleDataBlockValid[TITLE_DATA_COUNT];
#endif
KeyValues *m_pLeaderboardData;
KeyValues::AutoDelete m_autodelete_pLeaderboardData;
#ifdef _X360
struct XPendingAsyncAward_t
{
float m_flStartTimestamp;
XOVERLAPPED m_xOverlapped;
PlayerLocal *m_pLocalPlayer;
enum Type_t
{
TYPE_ACHIEVEMENT,
TYPE_AVATAR_AWARD
};
Type_t m_eType;
union
{
TitleAchievementsDescription_t const *m_pAchievementDesc;
TitleAvatarAwardsDescription_t const *m_pAvatarAwardDesc;
};
union
{
XUSER_ACHIEVEMENT m_xAchievement;
XUSER_AVATARASSET m_xAvatarAsset;
};
};
static CUtlVector< XPendingAsyncAward_t * > s_arrPendingAsyncAwards;
void UpdatePendingAwardsState();
#elif !defined( NO_STEAM )
void UpdatePlayersSteamLogon();
STEAM_CALLBACK_MANUAL( PlayerLocal, Steam_OnUserStatsReceived, UserStatsReceived_t, m_CallbackOnUserStatsReceived );
STEAM_CALLBACK_MANUAL( PlayerLocal, Steam_OnPersonaStateChange, PersonaStateChange_t, m_CallbackOnPersonaStateChange );
STEAM_CALLBACK_MANUAL( PlayerLocal, Steam_OnServersConnected, SteamServersConnected_t, m_CallbackOnServersConnected );
STEAM_CALLBACK_MANUAL( PlayerLocal, Steam_OnServersDisconnected, SteamServersDisconnected_t, m_CallbackOnServersDisconnected );
#endif
void EvaluateAwardsStateBasedOnStats();
void LoadGuestsTitleData();
void OnProfileTitleDataLoaded( int iErrorCode );
protected:
};
//
// Players XWrite opportunities
//
// XWrite opportunities
enum MM_XWriteOpportunity
{
MMXWO_NONE = 0,
MMXWO_SETTINGS,
MMXWO_SESSION_STARTED,
MMXWO_CHECKPOINT,
MMXWO_SESSION_FINISHED
};
void SignalXWriteOpportunity( MM_XWriteOpportunity eXWO );
#endif

File diff suppressed because it is too large Load Diff

141
matchmaking/playermanager.h Normal file
View File

@@ -0,0 +1,141 @@
//========= Copyright <20> 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef _PLAYERMANAGER_H_
#define _PLAYERMANAGER_H_
class PlayerManager;
#include <list>
#include "utlvector.h"
#include "utlmap.h"
#ifndef SWDS
#include "player.h"
class PlayerManager: public IPlayerManager, public IMatchEventsSink
{
public :
PlayerManager();
virtual ~PlayerManager();
//IPlayerManager
public:
//
// EnableServersUpdate
// controls whether friends data is being updated in the background
//
virtual void EnableFriendsUpdate( bool bEnable );
//
// GetLocalPlayer
// returns a local player interface for a given controller index
//
virtual IPlayerLocal * GetLocalPlayer( int iController );
//
// GetNumFriends
// returns number of friends discovered and for which data is available
//
virtual int GetNumFriends();
//
// GetFriend
// returns player interface to the given friend or NULL if friend not found or not available
//
virtual IPlayerFriend * GetFriendByIndex( int index );
virtual IPlayerFriend * GetFriendByXUID( XUID xuid );
//
// FindPlayer
// returns player interface by player's XUID or NULL if friend not found or not available
//
virtual IPlayer * FindPlayer( XUID xuid );
// IMatchEventsSink
public:
virtual void OnEvent( KeyValues *pEvent );
public:
void OnGameUsersChanged();
void Update();
void OnLocalPlayerDisconnectedFromLive( int iCtrlr );
void RecomputePlayerXUIDs( char const *szNetwork );
PlayerFriend *FindPlayerFriend( XUID xuid );
PlayerLocal *FindPlayerLocal( XUID xuid );
void RequestStoreStats();
protected:
void MarkOldFriends();
void RemoveOldFriends();
void OnSigninChange( KeyValues *pEvent );
void OnLostConnectionToConsoleNetwork();
#if defined( _PS3 ) && !defined( NO_STEAM )
STEAM_CALLBACK( PlayerManager, Steam_OnPS3PSNStatusChange, PS3PSNStatusChange_t, m_CallbackOnPS3PSNStatusChange );
#endif
private:
void CreateFriendEnumeration( int iCtrlr );
void CreateLanSearch();
void UpdateLanSearch();
void ExecuteStoreStatsRequest();
// Players instances
PlayerLocal * mLocalPlayer[ XUSER_MAX_COUNT ]; // local Players are cached off here for convienience.
CUtlVector< PlayerFriend * > mFriendsList;
bool m_bUpdateEnabled; // whether data should be auto-updated
float m_flNextUpdateTime; // when next update cycle should occur
struct SFriendSearchData
{
SFriendSearchData() { memset( this, 0, sizeof( *this ) ); }
bool mSearchInProgress;
void * mFriendBuffer;
int mFriendBufferSize;
int mFriendsStartIndex;
#ifdef _X360
HANDLE mFriendEnumHandle;
XOVERLAPPED mFriendsOverlapped;
#endif
XUID mXuid;
};
SFriendSearchData m_searchData[ XUSER_MAX_COUNT ];
struct SLanSearchData_t
{
SLanSearchData_t() { memset( this, 0, sizeof( *this ) ); }
bool m_bSearchInProgress;
float m_flStartTime;
float m_flLastBroadcastTime;
};
SLanSearchData_t m_lanSearchData;
int m_searchesPending; // Number of searches in progress
bool m_bRequestStoreStats;
};
#else // SWDS
class PlayerManager: public IPlayerManager, public IMatchEventsSink
{
public:
// SWDS declaration is intentionally left stripped
virtual void Update() = 0;
};
#endif
extern class PlayerManager *g_pPlayerManager;
#endif // _PLAYERMANAGER_H_

Some files were not shown because too many files have changed in this diff Show More