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,423 @@
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "matchext_swarm.h"
#include "swarm.spa.h"
#include "utlvector.h"
#include "utlstringmap.h"
#include "fmtstr.h"
#include "filesystem.h"
#define g_pFileSystem g_pFullFileSystem
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CMatchExtSwarm::CMatchExtSwarm()
{
m_pKeyValues = NULL;
}
CMatchExtSwarm::~CMatchExtSwarm()
{
if ( m_pKeyValues )
{
m_pKeyValues->deleteThis();
m_pKeyValues = NULL;
}
}
static CMatchExtSwarm g_MatchExtSwarm;
CMatchExtSwarm *g_pMatchExtSwarm = &g_MatchExtSwarm;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMatchExtSwarm, IMatchExtSwarm,
IMATCHEXT_SWARM_INTERFACE, g_MatchExtSwarm );
//
// Implementation
//
void CMatchExtSwarm::ParseMissionFromFile( char const *szFile, bool bBuiltIn )
{
KeyValues *pMissionModes = m_pKeyValues->FindKey( "GameModes" );
KeyValues *pMissionsRoot = m_pKeyValues->FindKey( "Missions" );
if ( !pMissionsRoot || !pMissionModes )
return;
// See if we already have this mission
if ( m_mapFilesLoaded.Find( szFile ) != m_mapFilesLoaded.InvalidIndex() )
return;
m_mapFilesLoaded[ szFile ] = NULL;
KeyValues *missionKeys = new KeyValues( "mission" );
KeyValues::AutoDelete autodelete_missionKeys( missionKeys ); // allows for early error-return
bool bLoadResult = missionKeys->LoadFromFile( g_pFileSystem, szFile );
if ( !bLoadResult )
{
Warning( "MissionManager: Mission file \"%s\" is malformed, failed to parse.\n", szFile );
return;
}
const char *name = missionKeys->GetString( "name", NULL );
const char *campaignVersion = missionKeys->GetString( "version", NULL );
if ( !name || !campaignVersion )
{
Warning( "MissionManager: Mission file \"%s\" is missing name and version\n", szFile );
return;
}
// Check invalid characters
for ( char const *pCharCheck = name; *pCharCheck; ++ pCharCheck )
{
char const c = *pCharCheck;
if ( !( ( c >= 'a' && c <= 'z' ) ||
( c >= 'A' && c <= 'Z' ) ||
( c >= '0' && c <= '9' ) ) )
{
Warning( "MissionManager: Only alphanumeric characters allowed in mission name: \"%s\" in \"%s\"\n", name, szFile );
return;
}
}
for ( char const *pCharCheck = campaignVersion; *pCharCheck; ++ pCharCheck )
{
char const c = *pCharCheck;
if ( !( c >= '0' && c <= '9' ) )
{
Warning( "MissionManager: Only numeric characters allowed in mission version: \"%s\" in \"%s\"\n", campaignVersion, szFile );
return;
}
}
// Now go through the game modes
KeyValues *modes = missionKeys->FindKey( "modes" );
if ( !modes )
{
Warning( "MissionManager: Mission file \"%s\" is missing data for any game modes\n", szFile );
return;
}
CFmtStr sNameVersion( "%s_%s", name, campaignVersion );
// Don't allow duplicates
if ( m_mapMissionsLoaded.Find( name ) != m_mapMissionsLoaded.InvalidIndex() )
{
Warning( "MissionManager: Duplicate mission \"%s\" in file \"%s\", already loaded from \"%s\".\n",
name, szFile, m_mapMissionsLoaded[ name ]->GetString( "cfgfile" ) );
return;
}
DevMsg( "\tMission %s ver %s loading...\n", name, campaignVersion );
// Validate DisplayTitle and Description
char const *szDisplayTitle = missionKeys->GetString( "displaytitle" );
if ( !szDisplayTitle || !*szDisplayTitle )
missionKeys->SetString( "displaytitle", name );
// Set auto-generated fields
missionKeys->SetName( name );
missionKeys->SetInt( "builtin", bBuiltIn ? 1 : 0 );
missionKeys->SetString( "cfgfile", szFile );
missionKeys->SetString( "cfgtag", sNameVersion );
// Load game modes
int numGameModes = 0;
for ( KeyValues *modeName = pMissionModes->GetFirstTrueSubKey(); modeName; modeName = modeName->GetNextTrueSubKey() )
{
KeyValues *mode = modes->FindKey( modeName->GetName() );
if ( !mode )
continue;
int numChapters = 0;
while ( KeyValues *pChapterKey = mode->FindKey( CFmtStr( "%d", numChapters + 1 ) ) )
{
// Check required fields
char const *szMap = pChapterKey->GetString( "map" );
if ( !szMap || !*szMap )
{
Warning( "MissionManager: Mission file \"%s\" has invalid map specified for modes/%s/%s\n",
szFile, modeName->GetName(), pChapterKey->GetName() );
numChapters = 0;
break;
}
// DisplayName
char const *szDisplayName = pChapterKey->GetString( "displayname" );
if ( !szDisplayName || !*szDisplayName )
{
pChapterKey->SetString( "displayname", CFmtStr( "%s-%s", name, pChapterKey->GetName() ) );
}
// Image
char const *szImage = pChapterKey->GetString( "image" );
if ( !szImage || !*szImage )
{
pChapterKey->SetString( "image", "maps/unknown" );
}
// Set the automatic fields
pChapterKey->SetInt( "chapter", numChapters + 1 );
// This chapter was valid
++ numChapters;
}
if ( !numChapters )
{
modes->RemoveSubKey( mode );
mode->deleteThis();
mode = NULL;
Warning( "MissionManager: Mission file \"%s\" has invalid settings for game mode %s\n",
szFile, modeName->GetName() );
continue;
}
mode->SetInt( "chapters", numChapters );
DevMsg( "\t\tloaded %d %s chapters.\n", numChapters, modeName->GetName() );
++ numGameModes;
}
if ( !numGameModes )
{
Warning( "MissionManager: Mission file \"%s\" does not have valid data for any supported game mode\n", szFile );
return;
}
//
// Bind the loaded mission keys into the system
//
m_mapFilesLoaded[ szFile ] = missionKeys;
m_mapMissionsLoaded[ name ] = missionKeys;
pMissionsRoot->AddSubKey( missionKeys );
autodelete_missionKeys.Assign( NULL ); // prevent automatic deletion
// Register all the loaded game modes
for ( KeyValues *modeName = pMissionModes->GetFirstTrueSubKey(); modeName; modeName = modeName->GetNextTrueSubKey() )
{
KeyValues *mode = modes->FindKey( modeName->GetName() );
if ( !mode )
continue;
modeName->SetPtr( name, missionKeys );
}
DevMsg( "\tMission %s ver %s loaded %d game modes.\n", name, campaignVersion, numGameModes );
}
void CMatchExtSwarm::MakeGameModeCopy( char const *szGameMode, char const *szCopyName )
{
// Fix the GameModes key
if ( KeyValues *pKeyMode = m_pKeyValues->FindKey( CFmtStr( "GameModes/%s", szGameMode ) ) )
{
pKeyMode = pKeyMode->MakeCopy();
pKeyMode->SetName( szCopyName );
m_pKeyValues->FindKey( "GameModes" )->AddSubKey( pKeyMode );
}
// Fix all missions
KeyValues *pMission = GetAllMissions();
for ( pMission = pMission ? pMission->GetFirstTrueSubKey() : NULL;
pMission; pMission = pMission->GetNextTrueSubKey() )
{
if ( KeyValues *pKeyMode = pMission->FindKey( CFmtStr( "modes/%s", szGameMode ) ) )
{
pKeyMode = pKeyMode->MakeCopy();
pKeyMode->SetName( szCopyName );
pMission->FindKey( "modes" )->AddSubKey( pKeyMode );
}
}
}
void CMatchExtSwarm::Initialize()
{
DevMsg( "Loading Mission Data\n" );
MEM_ALLOC_CREDIT();
if ( m_pKeyValues )
{
m_pKeyValues->deleteThis();
m_pKeyValues = NULL;
}
m_mapFilesLoaded.Purge();
m_mapMissionsLoaded.Purge();
m_pKeyValues = KeyValues::FromString(
"AlienSwarm",
" GameModes { "
" coop { } "
#ifndef _DEMO
" versus { } "
" survival { } "
" scavenge { } "
#endif
" } "
" Missions { "
// read from mission files
" } "
);
//
// Parse built-in missions
//
#ifndef _DEMO
ParseMissionFromFile( "missions/campaign1.txt", true );
ParseMissionFromFile( "missions/campaign2.txt", true );
ParseMissionFromFile( "missions/campaign3.txt", true );
ParseMissionFromFile( "missions/campaign4.txt", true );
ParseMissionFromFile( "missions/campaign5.txt", true );
ParseMissionFromFile( "missions/credits.txt", true );
//
// Search missions using the wildcards
//
char szMissionPath[_MAX_PATH];
Q_snprintf( szMissionPath, sizeof( szMissionPath ), "missions/*.txt" );
Q_FixSlashes( szMissionPath );
FileFindHandle_t handle;
const char *pFoundFile = g_pFileSystem->FindFirst( szMissionPath, &handle );
while ( pFoundFile )
{
char pFilename[ MAX_PATH ];
V_snprintf( pFilename, ARRAYSIZE(pFilename), "missions/%s", pFoundFile );
pFoundFile = g_pFileSystem->FindNext( handle );
ParseMissionFromFile( pFilename, false );
}
#else
ParseMissionFromFile( "missions/demo.txt", true );
#endif
#ifndef _DEMO
// Make game mode copies
MakeGameModeCopy( "versus", "teamversus" );
MakeGameModeCopy( "scavenge", "teamscavenge" );
MakeGameModeCopy( "coop", "realism" );
#endif
DevMsg( "Loading Mission Data Finished\n" );
}
void CMatchExtSwarm::DebugPrint()
{
KeyValuesDumpAsDevMsg( m_pKeyValues, 1, 0 );
}
//--------------------------------------------------------------------------------------------------------
CON_COMMAND( mission_reload, "Reload mission metadata" )
{
g_MatchExtSwarm.Initialize();
}
CON_COMMAND_F( mission_debug_print, "Print all mission metadata", FCVAR_DEVELOPMENTONLY )
{
g_MatchExtSwarm.DebugPrint();
}
KeyValues * CMatchExtSwarm::GetAllMissions()
{
if ( !m_pKeyValues )
return NULL;
return m_pKeyValues->FindKey( "Missions" );
}
// Get server map information for the session settings
KeyValues * CMatchExtSwarm::GetMapInfo( KeyValues *pSettings, KeyValues **ppMissionInfo )
{
if ( !m_pKeyValues )
return NULL;
char const *szGameMode = pSettings->GetString( "game/mode", NULL );
if ( !szGameMode || !*szGameMode )
return NULL;
char const *szCampaign = pSettings->GetString( "game/campaign", NULL );
if ( !szCampaign || !*szCampaign )
return NULL;
int nMapNumber = pSettings->GetInt( "game/chapter", 0 );
if ( nMapNumber <= 0 )
return NULL;
// Find the campaign key
KeyValues *pMissionKey = ( KeyValues * ) m_pKeyValues->GetPtr( CFmtStr( "GameModes/%s/%s", szGameMode, szCampaign ), NULL );
if ( !pMissionKey )
return NULL;
// Find the total number of chapters in that mission's game mode
int numChapters = pMissionKey->GetInt( CFmtStr( "modes/%s/chapters", szGameMode ), 0 );
if ( nMapNumber > numChapters )
return NULL;
KeyValues *pChapterKey = pMissionKey->FindKey( CFmtStr( "modes/%s/%d", szGameMode, nMapNumber ) );
if ( !pChapterKey )
return NULL;
if ( ppMissionInfo )
*ppMissionInfo = pMissionKey;
return pChapterKey;
}
KeyValues * CMatchExtSwarm::GetMapInfoByBspName( KeyValues *pSettings, char const *szBspMapName, KeyValues **ppMissionInfo )
{
if ( !m_pKeyValues )
return NULL;
Assert( szBspMapName );
if ( !szBspMapName || !*szBspMapName )
return NULL;
char const *szGameMode = pSettings->GetString( "game/mode", NULL );
if ( !szGameMode || !*szGameMode )
return NULL;
// Walk all the missions in that game mode
KeyValues *pModeMissions = m_pKeyValues->FindKey( CFmtStr( "GameModes/%s", szGameMode ) );
if ( !pModeMissions )
return NULL;
for ( KeyValues *pMissionName = pModeMissions->GetFirstValue(); pMissionName; pMissionName = pMissionName->GetNextValue() )
{
KeyValues *pMission = ( KeyValues * ) pMissionName->GetPtr();
if ( !pMission )
continue;
KeyValues *pChapters = pMission->FindKey( CFmtStr( "modes/%s", szGameMode ) );
if ( !pChapters )
continue;
int numChapters = pChapters->GetInt( "chapters" );
for ( int k = 1; k <= numChapters; ++ k )
{
KeyValues *pMap = pChapters->FindKey( CFmtStr( "%d", k ) );
if ( !pMap )
break;
char const *szBspName = pMap->GetString( "map" );
if ( !Q_stricmp( szBspName, szBspMapName ) )
{
if ( ppMissionInfo )
*ppMissionInfo = pMission;
return pMap;
}
}
}
return NULL;
}

View File

@@ -0,0 +1,45 @@
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef MATCHEXT_SWARM_H
#define MATCHEXT_SWARM_H
#ifdef _WIN32
#pragma once
#endif
#include "../mm_framework.h"
#include "matchmaking/swarm/imatchext_swarm.h"
class CMatchExtSwarm : public IMatchExtSwarm
{
// Methods of IMatchExtSwarm
public:
// Get server map information for the session settings
virtual KeyValues * GetAllMissions();
virtual KeyValues * GetMapInfo( KeyValues *pSettings, KeyValues **ppMissionInfo = NULL );
virtual KeyValues * GetMapInfoByBspName( KeyValues *pSettings, char const *szBspMapName, KeyValues **ppMissionInfo = NULL );
public:
void Initialize();
void DebugPrint();
void ParseMissionFromFile( char const *szFile, bool bBuiltIn );
void MakeGameModeCopy( char const *szGameMode, char const *szCopyName );
public:
CMatchExtSwarm();
~CMatchExtSwarm();
private:
KeyValues *m_pKeyValues;
CUtlStringMap< KeyValues * > m_mapFilesLoaded;
CUtlStringMap< KeyValues * > m_mapMissionsLoaded;
};
// Match extension singleton
extern CMatchExtSwarm *g_pMatchExtSwarm;
#endif // MATCHEXT_L4D_H

View File

@@ -0,0 +1,462 @@
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "mm_title_richpresence.h"
#include "swarm.spa.h"
#include "matchext_swarm.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 );
}
// Initialize all the campaigns
g_pMatchExtSwarm->Initialize();
#ifdef _X360
// Initialize Title Update version
extern ConVar mm_tu_string;
mm_tu_string.SetValue( "00000000" );
#endif
return INIT_OK;
}
void CMatchTitle::Shutdown()
{
if ( IGameEventManager2 *mgr = g_pMatchExtensions->GetIGameEventManager2() )
{
mgr->RemoveListener( this );
}
}
//
// Implementation
//
uint64 CMatchTitle::GetTitleID()
{
#ifdef _X360
#ifndef _DEMO
return TITLEID_SWARM;
#else
return TITLEID_SWARM_DEMO;
#endif
#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 = 128; // 512 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()
{
// Alien Swarm is a 4-player game
return 4;
}
// 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
KeyValues *pInfoMission = NULL;
KeyValues *pInfoChapter = g_pMatchExtSwarm->GetMapInfo( pSettings, &pInfoMission );
Assert( pInfoChapter );
if ( !pInfoChapter || !pInfoMission )
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( pInfoChapter->GetString( "map" ) ) )
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;
}
#ifndef SWDS
static void SendPreConnectClientDataToServer( int nSlot )
{
// We have just connected to the server,
// send our avatar information
int iController = nSlot;
#ifdef _X360
iController = XBX_GetUserId( nSlot );
#endif
// Swarm for now has no preconnect data
if ( 1 )
return;
KeyValues *pPreConnectData = new KeyValues( "preconnectdata" );
//
// Now we prep the keyvalues for server
//
if ( IPlayerLocal *pPlayerLocal = g_pPlayerManager->GetLocalPlayer( iController ) )
{
// Set session-specific user info
XUID xuid = pPlayerLocal->GetXUID();
pPreConnectData->SetUint64( "xuid", xuid );
KeyValues *pSettings = GetCurrentMatchSessionSettings();
KeyValues *pMachine = NULL;
if ( KeyValues *pPlayer = SessionMembersFindPlayer( pSettings, xuid, &pMachine ) )
{
}
}
// Deliver the keyvalues to the server
int nRestoreSlot = g_pMatchExtensions->GetIVEngineClient()->GetActiveSplitScreenPlayerSlot();
g_pMatchExtensions->GetIVEngineClient()->SetActiveSplitScreenPlayerSlot( nSlot );
g_pMatchExtensions->GetIVEngineClient()->ServerCmdKeyValues( pPreConnectData );
g_pMatchExtensions->GetIVEngineClient()->SetActiveSplitScreenPlayerSlot( nRestoreSlot );
}
#endif
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 );
}
}
#ifndef SWDS
else if ( !Q_stricmp( "OnEngineClientSignonStateChange", szEvent ) )
{
int nSlot = pEvent->GetInt( "slot" );
int iOldState = pEvent->GetInt( "old" );
int iNewState = pEvent->GetInt( "new" );
if ( iOldState < SIGNONSTATE_CONNECTED &&
iNewState >= SIGNONSTATE_CONNECTED )
{
SendPreConnectClientDataToServer( nSlot );
}
}
else if ( !Q_stricmp( "OnEngineSplitscreenClientAdded", szEvent ) )
{
int nSlot = pEvent->GetInt( "slot" );
SendPreConnectClientDataToServer( nSlot );
}
else if ( !Q_stricmp( "Client::CmdKeyValues", szEvent ) )
{
KeyValues *pCmd = pEvent->GetFirstTrueSubKey();
if ( !pCmd )
return;
char const *szCmd = pCmd->GetName();
szCmd;
}
#endif
}
//
//
//
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;
char const *szGameMode = pSessionSettings->GetString( "game/mode", "coop" );
szGameMode;
// 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( "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 );
KeyValues *pInfoMission = NULL;
KeyValues *pInfoChapter = g_pMatchExtSwarm->GetMapInfoByBspName( pSessionSettings, szMapName, &pInfoMission );
Assert( pInfoChapter && pInfoMission );
if ( pInfoChapter && pInfoMission )
{
kvUpdate->SetString( "update/game/campaign", pInfoMission->GetString( "name" ) );
kvUpdate->SetInt( "update/game/chapter", pInfoChapter->GetInt( "chapter" ) );
}
pMatchSession->UpdateSessionSettings( kvUpdate );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues(
"OnProfilesWriteOpportunity", "reason", "checkpoint"
) );
}
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 © 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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "../mm_title_main.h"
#include "matchext_swarm.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,241 @@
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title_richpresence.h"
#include "swarm.spa.h"
#include "matchext_swarm.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//
// Mapping of context values
//
ContextValue_t g_pcv_CONTEXT_GAME_MODE[] = {
{ "versus", CONTEXT_GAME_MODE_VERSUS },
{ "teamversus", CONTEXT_GAME_MODE_TEAMVERSUS },
{ "scavenge", CONTEXT_GAME_MODE_SCAVENGE },
{ "teamscavenge", CONTEXT_GAME_MODE_TEAMSCAVENGE },
{ "survival", CONTEXT_GAME_MODE_SURVIVAL },
{ "realism", CONTEXT_GAME_MODE_REALISM },
{ NULL, CONTEXT_GAME_MODE_COOP },
};
ContextValue_t g_pcv_CONTEXT_STATE[] = {
{ "game", CONTEXT_STATE_GAME },
{ "finale", CONTEXT_STATE_FINALE },
{ NULL, CONTEXT_STATE_LOBBY },
};
ContextValue_t g_pcv_CONTEXT_DIFFICULTY[] = {
{ "easy", CONTEXT_DIFFICULTY_EASY },
{ "hard", CONTEXT_DIFFICULTY_HARD },
{ "impossible", CONTEXT_DIFFICULTY_IMPOSSIBLE },
{ NULL, CONTEXT_DIFFICULTY_NORMAL },
};
ContextValue_t g_pcv_CONTEXT_ACCESS[] = {
{ "public", CONTEXT_ACCESS_PUBLIC },
{ "friends", CONTEXT_ACCESS_FRIENDS },
{ NULL, CONTEXT_ACCESS_PRIVATE },
};
//
// User context and property setting
//
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 )
{
if ( char const *szValue = pSettings->GetString( "game/mode", NULL ) )
{
SetAllUsersContext( X_CONTEXT_GAME_MODE, g_pcv_CONTEXT_GAME_MODE->ScanValues( szValue ), false );
}
// matchmaking version
{
static int val; // must be valid for the async call
extern ConVar mm_matchmaking_version;
val = mm_matchmaking_version.GetInt();
SetAllUsersProperty( PROPERTY_MMVERSION, sizeof( val ), &val );
DevMsg( "PrepareForSessionCreate: matchmaking version %d\n", val );
}
return NULL;
}
void MM_Title_RichPresence_Update( KeyValues *pFullSettings, KeyValues *pUpdatedSettings )
{
if ( !pFullSettings )
{
SetAllUsersContext( X_CONTEXT_PRESENCE, 1 ); // main menu
return;
}
// Also set players information during initial rich presence update
if ( !pUpdatedSettings && pFullSettings )
{
MM_Title_RichPresence_PlayersChanged( pFullSettings );
}
// pUpdatedSettings = NULL when the session is created and all contexts need to be set
KeyValues *pNewSettings = pUpdatedSettings ? pUpdatedSettings : pFullSettings;
// Current mission/map (can be NULL!)
char const *szCampaign = pFullSettings->GetString( "game/campaign", "" );
KeyValues *pInfoMission = szCampaign[0] ? g_pMatchExtSwarm->GetAllMissions()->FindKey( szCampaign ) : NULL;
KeyValues *pInfoChapter = g_pMatchExtSwarm->GetMapInfo( pFullSettings );
if ( char const *szValue = pNewSettings->GetString( "system/access", NULL ) )
{
SetAllUsersContext( CONTEXT_ACCESS, g_pcv_CONTEXT_ACCESS->ScanValues( szValue ) );
}
if ( char const *szValue = pNewSettings->GetString( "game/state", NULL ) )
{
SetAllUsersContext( CONTEXT_STATE, g_pcv_CONTEXT_STATE->ScanValues( szValue ) );
}
if ( char const *szValue = pNewSettings->GetString( "game/mode", NULL ) )
{
SetAllUsersContext( X_CONTEXT_GAME_MODE, g_pcv_CONTEXT_GAME_MODE->ScanValues( szValue ) );
}
if ( char const *szValue = pNewSettings->GetString( "game/difficulty", NULL ) )
{
SetAllUsersContext( CONTEXT_DIFFICULTY, g_pcv_CONTEXT_DIFFICULTY->ScanValues( szValue ) );
}
if ( KeyValues *kvVal = pNewSettings->FindKey( "game/maxrounds" ) )
{
static int val; // must be valid for the async call
val = kvVal->GetInt();
SetAllUsersProperty( PROPERTY_MAXROUNDS, sizeof( val ), &val );
}
bool bSetLevelDescription = false;
if ( char const *szValue = pNewSettings->GetString( "game/campaign", NULL ) )
{
int iCampaign = pInfoMission->GetInt( "x360ctx", CONTEXT_CAMPAIGN_ANY );
SetAllUsersContext( CONTEXT_CAMPAIGN, iCampaign );
bSetLevelDescription = true;
}
if ( KeyValues *kvVal = pNewSettings->FindKey( "game/chapter" ) )
{
static int val; // must be valid for the async call
val = kvVal->GetInt();
SetAllUsersProperty( PROPERTY_CHAPTER, sizeof( val ), &val );
bSetLevelDescription = true;
}
if ( bSetLevelDescription )
{
int iContext = CONTEXT_LEVELDESCRIPTION_ANY;
if ( pInfoChapter )
iContext = pInfoChapter->GetInt( "x360ctx", iContext );
else if ( pInfoMission )
iContext = pInfoMission->GetInt( "x360ctx", iContext ); // mission contexts correspond to chapters too
SetAllUsersContext( CONTEXT_LEVELDESCRIPTION, iContext );
}
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_MMVERSION + k, sizeof( val ), &val );
}
}
//
// Determine Rich Presence Display
//
if ( char const *szGameModeForRichPresence = pFullSettings->GetString( "game/mode", NULL ) )
{
unsigned int dwLevelPresence = CONTEXT_PRESENCE_MAINMENU;
static ContextValue_t values[] = {
{ "versus", CONTEXT_PRESENCE_MODE_GAP_MISSION_LOBBY },
{ "teamversus", CONTEXT_PRESENCE_MODE_GAP_MISSION_LOBBY },
{ "survival", CONTEXT_PRESENCE_MODE_GAP_LEVEL_LOBBY },
{ "scavenge", CONTEXT_PRESENCE_MODE_GAP_LEVEL_LOBBY },
{ "teamscavenge", CONTEXT_PRESENCE_MODE_GAP_LEVEL_LOBBY },
{ "coop", CONTEXT_PRESENCE_MODE_DIFF_MISSION_LOBBY },
{ "realism", CONTEXT_PRESENCE_MODE_DIFF_MISSION_LOBBY },
{ NULL, CONTEXT_PRESENCE_MAINMENU },
};
dwLevelPresence = values->ScanValues( szGameModeForRichPresence );
// Hook for offline game
if ( !Q_stricmp( "offline", pFullSettings->GetString( "system/network" ) ) )
dwLevelPresence = CONTEXT_PRESENCE_LOCAL_DIFF_MISSION_SETTINGS;
// Special hook for developers commentary
if ( !Q_stricmp( "commentary", pFullSettings->GetString( "options/play" ) ) )
dwLevelPresence = CONTEXT_PRESENCE_DEVELOPER_COMM_SETTINGS;
// If the game is active, then use the +1 presence
if ( Q_stricmp( "lobby", pFullSettings->GetString( "game/state", "lobby" ) ) )
++ dwLevelPresence;
dwLevelPresence = pInfoMission->GetInt( "x360presence", dwLevelPresence ); // let the mission override
dwLevelPresence = pInfoChapter->GetInt( "x360presence", dwLevelPresence ); // let the chapter override
SetAllUsersContext( X_CONTEXT_PRESENCE, dwLevelPresence );
}
}
void MM_Title_RichPresence_PlayersChanged( KeyValues *pFullSettings )
{
if ( int numPlayers = pFullSettings->GetInt( "members/numPlayers" ) )
{
static int val; // must be valid for the async call
val = numPlayers;
SetAllUsersProperty( PROPERTY_NUMPLAYERS, sizeof( val ), &val );
}
}

View File

@@ -0,0 +1,25 @@
//===== Copyright © 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 );
extern ContextValue_t g_pcv_CONTEXT_ACCESS[];
extern ContextValue_t g_pcv_CONTEXT_GAME_MODE[];
extern ContextValue_t g_pcv_CONTEXT_STATE[];
extern ContextValue_t g_pcv_CONTEXT_DIFFICULTY[];
#endif // MM_TITLE_H

View File

@@ -0,0 +1,143 @@
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_title.h"
#include "matchext_swarm.h"
#include "swarm.spa.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
// ACHIEVEMENTS
TD_ENTRY( "TD2.COUNT.ACH_BEAT_CAMPAIGNS_EXPERT_MODE", DB_TD2, DT_U8, offsetof( TitleData2, iCountBeatCampaignsExpertMode ) ),
TD_ENTRY( "TD2.COUNT.ACH_CUT_OFF_HEADS_MELEE", DB_TD2, DT_U8, offsetof( TitleData2, iCountCutOffHeadsMelee ) ),
TD_ENTRY( "TD2.COUNT.ACH_KILL_WITH_EVERY_MELEE", DB_TD2, DT_U8, offsetof( TitleData2, iCountKillWithEveryMelee ) ),
TD_ENTRY( "TD2.COUNT.ACH_KILL_INFECTED_WITH_CHAINSAW", DB_TD2, DT_U8, offsetof( TitleData2, iCountKillInfectedWithChainsaw ) ),
TD_ENTRY( "TD2.COUNT.ACH_RES_SURVIVORS_WITH_DEFIB", DB_TD2, DT_U8, offsetof( TitleData2, iCountResSurvivorsWithDefib ) ),
TD_ENTRY( "TD2.COUNT.ACH_SPEED_REVIVE_WITH_ADRENALINE", DB_TD2, DT_U8, offsetof( TitleData2, iCountSpeedReviveWithAdrenaline ) ),
TD_ENTRY( "TD2.COUNT.ACH_IGNITE_INFECTED_FIRE_AMMO", DB_TD2, DT_U8, offsetof( TitleData2, iCountIgniteInfectedFireAmmo ) ),
TD_ENTRY( "TD2.COUNT.ACH_KILL_EVERY_UNCOMMON_INFECTED", DB_TD2, DT_U8, offsetof( TitleData2, iCountKillEveryUncommonInfected ) ),
TD_ENTRY( "TD2.COUNT.ACH_KILL_SUBMERGED_MUDMEN", DB_TD2, DT_U8, offsetof( TitleData2, iCountKillSubmergedMudmen ) ),
TD_ENTRY( "TD2.COUNT.ACH_COLLECT_CEDA_VIALS", DB_TD2, DT_U8, offsetof( TitleData2, iCountCollectCEDAVials ) ),
TD_ENTRY( "TD2.COUNT.ACH_SCAVENGE_COLLECT_CAN_GRIND", DB_TD2, DT_U8, offsetof( TitleData2, iCountScavengeCollectCanGrind ) ),
TD_ENTRY( "TD2.COUNT.ACH_SCAVENGE_CAN_DROP_GRIND", DB_TD2, DT_U8, offsetof( TitleData2, iCountScavengeCanDropGrind ) ),
TD_ENTRY( "TD2.COUNT.ACH_HONK_A_CLOWNS_NOSE", DB_TD2, DT_U8, offsetof( TitleData2, iCountHonkAClownsNose ) ),
TD_ENTRY( "TD2.COMP.ACH_BEAT_CAMPAIGNS_EXPERT_MODE", DB_TD2, DT_U8, offsetof( TitleData2, iCompBeatCampaignsExpertMode ) ),
TD_ENTRY( "TD2.COMP.ACH_KILL_WITH_EVERY_MELEE", DB_TD2, DT_U16, offsetof( TitleData2, iCompKillWithEveryMelee ) ),
TD_ENTRY( "TD2.COMP.ACH_KILL_EVERY_UNCOMMON_INFECTED", DB_TD2, DT_U16, offsetof( TitleData2, iCompKillEveryUncommonInfected ) ),
// AVATAR AWARDS
TD_ENTRY( "TD2.COUNT.ASSET_MED_KIT", DB_TD2, DT_U8, offsetof( TitleData2, iCountBeatCampaignsAnyMode ) ),
TD_ENTRY( "TD2.COMP.ASSET_MED_KIT", DB_TD2, DT_U8, offsetof( TitleData2, iCompBeatCampaignsAnyMode ) ),
TD_ENTRY( "TD2.COUNT.ASSET_PIPE_BOMB", DB_TD2, DT_U16, offsetof( TitleData2, iCountKillTenThousandZombies ) ),
TD_ENTRY( "TD2.COUNT.ASSET_GAS_CAN", DB_TD2, DT_U8, offsetof( TitleData2, iCountWinScavengerMatches ) ),
TD_ENTRY( "TD2.COUNT.ASSET_FRYING_PAN", DB_TD2, DT_U8, offsetof( TitleData2, iCountWinVersusMatches ) ),
// ZOMBIE PANEL SEEN COUNTS
TD_ENTRY( "TD2.COUNT.ZOMBIE_PANEL_INTRO", DB_TD2, DT_U8, offsetof( TitleData2, iCountZombiePanelIntro ) ),
TD_ENTRY( "TD2.COUNT.ZOMBIE_PANEL_SMOKER", DB_TD2, DT_U8, offsetof( TitleData2, iCountZombiePanelSmoker ) ),
TD_ENTRY( "TD2.COUNT.ZOMBIE_PANEL_BOOMER", DB_TD2, DT_U8, offsetof( TitleData2, iCountZombiePanelBoomer ) ),
TD_ENTRY( "TD2.COUNT.ZOMBIE_PANEL_HUNTER", DB_TD2, DT_U8, offsetof( TitleData2, iCountZombiePanelHunter ) ),
TD_ENTRY( "TD2.COUNT.ZOMBIE_PANEL_SPITTER", DB_TD2, DT_U8, offsetof( TitleData2, iCountZombiePanelSpitter ) ),
TD_ENTRY( "TD2.COUNT.ZOMBIE_PANEL_JOCKEY", DB_TD2, DT_U8, offsetof( TitleData2, iCountZombiePanelJockey ) ),
TD_ENTRY( "TD2.COUNT.ZOMBIE_PANEL_CHARGER", DB_TD2, DT_U8, offsetof( TitleData2, iCountZombiePanelCharger ) ),
#endif
// END MARKER
TD_ENTRY( NULL, DB_TD1, DT_U8, 0 )
};
#undef TD_ENTRY
return tdfd;
}
TitleAchievementsDescription_t const * CMatchTitle::DescribeTitleAchievements()
{
static TitleAchievementsDescription_t tad[] =
{
#include "swarm.xhelp.achtitledesc.txt"
// END MARKER
{ NULL, 0 }
};
return tad;
}
TitleAvatarAwardsDescription_t const * CMatchTitle::DescribeTitleAvatarAwards()
{
static TitleAvatarAwardsDescription_t taad[] =
{
#include "swarm.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_pMatchExtSwarm->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;
}