You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
510 lines
14 KiB
510 lines
14 KiB
//===== Copyright © 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"
|
|
) );
|
|
}
|
|
}
|
|
}
|
|
|