summaryrefslogtreecommitdiff
path: root/game/server/tf/tf_gc_server.h
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/tf_gc_server.h')
-rw-r--r--game/server/tf/tf_gc_server.h539
1 files changed, 539 insertions, 0 deletions
diff --git a/game/server/tf/tf_gc_server.h b/game/server/tf/tf_gc_server.h
new file mode 100644
index 0000000..788fd34
--- /dev/null
+++ b/game/server/tf/tf_gc_server.h
@@ -0,0 +1,539 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+#ifndef TF_GC_SERVER_H
+#define TF_GC_SERVER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#if !defined( _X360 ) && !defined( NO_STEAM )
+#include "steam/steam_api.h"
+#include "steam/steam_gameserver.h"
+#endif
+
+//#include "tf_gc_common.h"
+#include "gcsdk/gcclientsdk.h"
+#include "playergroup.h"
+//#include "dota_gamerules.h"
+#include "gc_clientsystem.h"
+#include "tf_gcmessages.h"
+#include "GameEventListener.h"
+#include "rtime.h"
+#include "tf_shareddefs.h"
+
+class CTFGSLobby;
+class CTFParty;
+
+//enum EDOTA_Uploading_Match_Stats
+//{
+// EDOTA_MATCH_STATS_IDLE,
+// EDOTA_MATCH_STATS_UPLOADING,
+// EDOTA_MATCH_STATS_UPLOAD_COMPLETE
+//};
+
+#ifdef ENABLE_GC_MATCHMAKING
+
+class CMvMVictoryInfo
+{
+public:
+ int m_nLobbyId;
+ CUtlString m_sChallengeName;
+#ifdef USE_MVM_TOUR
+ CUtlString m_sMannUpTourOfDuty;
+#endif // USE_MVM_TOUR
+ CUtlVector<uint64> m_vPlayerIds;
+ CUtlVector<bool> m_vSquadSurplus;
+ RTime32 m_tEventTime;
+
+ void Init ( CTFGSLobby *pLobby );
+};
+
+class CMatchInfo
+{
+ friend class CTFGCServerSystem;
+public:
+ CMatchInfo( const CTFGSLobby *pLobby );
+ ~CMatchInfo();
+
+ uint64 m_nMatchID;
+ uint64 m_nLobbyID;
+ EMatchGroup m_eMatchGroup;
+ uint32 m_uLobbyFlags;
+ uint32 m_uAverageRank;
+ RTime32 m_rtMatchCreated;
+ uint32 m_unEventTeamStatus;
+ bool m_bFirstPersonActive;
+ int m_nBotsAdded;
+ bool m_bServerCreated;
+
+ struct DailyStatsRankBucket_t
+ {
+ uint32 nRank;
+ uint32 nRecords;
+ uint32 nAvgScore;
+ uint32 nStDevScore;
+ uint32 nAvgKills;
+ uint32 nStDevKills;
+ uint32 nAvgDamage;
+ uint32 nStDevDamage;
+ uint32 nAvgHealing;
+ uint32 nStDevHealing;
+ uint32 nAvgSupport;
+ uint32 nStDevSupport;
+ };
+
+ struct PlayerMatchData_t
+ {
+ friend class CTFGCServerSystem;
+ friend class CMatchInfo;
+ PlayerMatchData_t( CSteamID steamID, const CTFLobbyMember *pMemberData )
+ : steamID( steamID )
+ , uPartyID( pMemberData->party_id() )
+ , eGCTeam( pMemberData->team() )
+ , bDropped( false )
+ , bConnected( false )
+ , rtJoinedMatch( CRTime::RTime32TimeCur() )
+ , nVoteKickAttempts( 0 )
+ , nDisconnectedSeconds( 0 )
+ , nScoreMedal( 0 )
+ , nKillsMedal( 0 )
+ , nDamageMedal( 0 )
+ , nHealingMedal( 0 )
+ , nSupportMedal( 0 )
+ , bLateJoin( false )
+ , nScore( 0 )
+ , bPlayed( false )
+ , unMMSkillRating( 0u )
+ , nDrilloRatingDelta( 0 )
+ , unClassesPlayed( 0u )
+ , rtLastActiveEvent( CRTime::RTime32TimeCur() )
+ , bAlwaysSafeToLeave( false )
+ , bEverConnected( false )
+ , bDropWasAbandon( false )
+ , eDropReason( TFMatchLeaveReason_UNSPECIFIED )
+ , nConnectingButNotActiveIndex( 0 )
+ , m_mapXPAccumulation( DefLessFunc( CMsgTFXPSource::XPSourceType ) )
+ {}
+
+ PlayerMatchData_t( const PlayerMatchData_t& rhs );
+
+ CSteamID steamID;
+ uint64 uPartyID;
+ TF_GC_TEAM eGCTeam;
+
+ // If true, this player was dropped from the match and is not part of the active lobby. This is important for
+ // cases where the GC connection is lost and the lobby state is stale.
+ bool bDropped;
+ bool bConnected;
+ // Timestamp player joined the match at. Not guaranteed to be the same instant the match was created, depending
+ // on how the GC does things.
+ RTime32 rtJoinedMatch;
+
+ uint32 nVoteKickAttempts;
+
+ // Number of cumulative seconds the player has been absent, *not* including the initial connect timeout. Used
+ // to determine when to award an abandon. We may do odd things like "comp" you some seconds on a second, later,
+ // disconnect, so this shouldn't be used for stats purposes.
+ int nDisconnectedSeconds;
+
+ int nScoreMedal;
+ int nKillsMedal;
+ int nDamageMedal;
+ int nHealingMedal;
+ int nSupportMedal;
+
+ bool bLateJoin;
+ int nScore;
+ bool bPlayed;
+ // This is a single-value skill rating given to each player by the GC
+ uint32 unMMSkillRating;
+ // This is the older drillo rating system that was done on the server. It is still sent up to the GC as the
+ // input to the drillo backend there. If we want to keep this long-term it should be moved to be a fully-gc
+ // backend like glicko
+ int nDrilloRatingDelta;
+ uint32 unClassesPlayed;
+
+ const CMsgTFXPSourceBreakdown& GetXPSources() const { return m_XPBreakdown; }
+
+ // Override releasing this player from obligation to this match beyond the normal abandon rules. Used by MvM mode
+ // for marking everyone who completes a wave as allowed to drop without penalty, for instance.
+ void MarkAlwaysSafeToLeave() { bAlwaysSafeToLeave = true; }
+
+ bool BDropWasAbandon() { return bDropped && bDropWasAbandon; }
+ TFMatchLeaveReason GetDropReason() { return bDropped ? eDropReason : TFMatchLeaveReason_UNSPECIFIED; }
+ RTime32 GetLastActiveEventTime( void ) { return rtLastActiveEvent; }
+
+ MM_PlayerConnectionState_t GetConnectionState() const;
+ void UpdateClassesPlayed( int nClass );
+
+ struct XPBonusPool_t
+ {
+ XPBonusPool_t()
+ : m_flMultiplier( 1.f )
+ , m_nBonusPoolRemaining( 0 )
+ {}
+
+ CMsgTFXPSource_XPSourceType m_eType;
+
+ // Only give up to this amount
+ int m_nBonusPoolRemaining;
+ // Give at this rate
+ float m_flMultiplier;
+ };
+
+ private:
+
+ void OnConnected( int nEntindex );
+ void OnActive();
+
+ // Last time the player changed between active (fully loaded in) and not-active. A player is active if
+ // ( bConnected && !nConnectingButNotActiveIndex )
+ RTime32 rtLastActiveEvent;
+ bool bAlwaysSafeToLeave;
+ bool bEverConnected;
+ // If dropped - was it an abandon and what was the reason.
+ bool bDropWasAbandon;
+ TFMatchLeaveReason eDropReason;
+
+ // Track the janky source-engine state between ClientConnect (when we allow them in) and ClientActive
+ int nConnectingButNotActiveIndex;
+
+ // XP accumulation for a player
+ CMsgTFXPSourceBreakdown m_XPBreakdown;
+ // The breakdown stores ints, but we need float precision or else we're going to round off a
+ // significant amount of XP as the match plays on.
+ CUtlMap< CMsgTFXPSource::XPSourceType, float > m_mapXPAccumulation;
+
+ CUtlVector< XPBonusPool_t > m_vecXPBonusPools;
+ };
+
+ enum RankStatType_t
+ {
+ RankStat_Invalid = -1,
+ RankStat_Score = 0,
+ RankStat_Kills,
+ RankStat_Damage,
+ RankStat_Healing,
+ RankStat_Support,
+ };
+
+ void SetDailyRankData( DailyStatsRankBucket_t vecRankData );
+ bool RequestGCRankData( void );
+ bool CalculatePlayerMatchRankData( void );
+ bool CalculateMatchSkillRatingAdjustments( int iWinningTeam );
+ const CMatchInfo::PlayerMatchData_t* GetMatchDataForPlayer( CSteamID steamID ) const;
+ CMatchInfo::PlayerMatchData_t* GetMatchDataForPlayer( CSteamID steamID );
+
+ // For iterating over all players. Index is relative to GetNumTotalMatchPlayers
+ CMatchInfo::PlayerMatchData_t* GetMatchDataForPlayer( int nPlayer );
+
+ // This is the total number of players we have match data for -- it may include dropped players not part of the
+ // match any longer.
+ int GetNumTotalMatchPlayers() const;
+
+ // Number of players still active in the match.
+ int GetNumActiveMatchPlayers() const;
+
+ // Number of players still active in the match for a specific team
+ int GetNumActiveMatchPlayersForTeam( int nTeam ) const;
+
+ // Total skill rating for a team
+ int GetTotalSkillRatingForTeam( int nTeam ) const;
+
+ // Subset of active match players who are currently connected
+ int GetNumConnectedMatchPlayers() const;
+
+ // Indicates that this is a stale match that is ending. In cases such as rolling matches, we never "end" a match,
+ // just roll into the next one, since "ended" matches indicate that we've terminated our relationship with the
+ // players/GC.
+ bool BMatchTerminated() const { return m_bMatchEnded; }
+
+ // Indicates we've sent a result for this match. The match may still be active if we're intending to use it to start
+ // a rolling match or other post-game activities.
+ bool BSentResult() const { return m_bSentResult; }
+
+ // The canonical size of this type of match. Can be passed from the GC and override the match description size.
+ uint32 GetCanonicalMatchSize() const;
+
+ const char *GetMatchMap() const { return m_strMapName.Length() ? m_strMapName.Get() : NULL; }
+
+ // Rewards the player with XP based on the count scaled by the XP per unit of that type.
+ // nCount here is the raw occurances of the action (ie. Points scored, Gold Medals Scored)
+ void GiveXPRewardToPlayerForAction( CSteamID steamID, CMsgTFXPSource::XPSourceType eType, int nCount );
+ // Directly assign the value
+ void GiveXPDirectly( CSteamID steamID, CMsgTFXPSource::XPSourceType eType, int nAmount, bool bCanAwardBonusXP = true );
+ // Give an XP bonus that increases
+ void GiveXPBonus( CSteamID steamID,
+ CMsgTFXPSource_XPSourceType eType,
+ float flMultipler,
+ int nBonusPool );
+
+ // Is this player allowed to leave the match without incurring an abandon right now
+
+ // TODO(JohnS): This should not go from true to false due to race conditions (players clicks DC, sees no warning,
+ // races with server deciding its unsafe again), but does for MvM late join. The GS-initiated late
+ // join rework would make it possible to fix that (once it enters too-low-to-latejoin state it stays
+ // there)
+ bool BPlayerSafeToLeaveMatch( CSteamID steamID );
+
+protected:
+ int GetRankForStat( RankStatType_t statType, int nRankIndex, uint32 nValue );
+ float NormalDistributionCDF( float flValue, float flMu, float flSigma );
+
+private:
+ CMatchInfo();
+ CMatchInfo( const CMatchInfo &otherinfo );
+
+ // Track a new player participating in our match
+ void AddPlayer( CSteamID steamID, const CTFLobbyMember *pMemberData, bool bIsLateJoin, int nEntindex, bool bActive );
+ // Or with an existing player to copy from (e.g. old match)
+ void AddPlayer( const PlayerMatchData_t &oldPlayer, int nEntIndex, bool bActive );
+
+ // Mark a player as dropped from the match
+ void DropPlayer( CSteamID steamID, TFMatchLeaveReason eReason, bool bWasAbandon );
+ void SetEnded() { m_bMatchEnded = true; }
+
+ CUtlVector < DailyStatsRankBucket_t > m_vDailyStatsRankData;
+ CUtlVector < PlayerMatchData_t* > m_vMatchRankData;
+
+ CUtlString m_strMapName;
+ bool m_bMatchEnded;
+ bool m_bSentResult;
+ // Canonical size for this match type, override passed from GC.
+ uint32 m_nGCMatchSize;
+
+ float m_flBronzePercentile;
+ float m_flSilverPercentile;
+ float m_flGoldPercentile;
+};
+
+class CTFGCServerSystem : public CGCClientSystem, public GCSDK::ISharedObjectListener, public IServerGCLobby, public CGameEventListener
+{
+ DECLARE_CLASS_GAMEROOT( CTFGCServerSystem, CGCClientSystem );
+
+ // Messages that need to do callbacks
+ friend class ReliableMsgNewMatchForLobby;
+ friend class ReliableMsgChangeMatchPlayerTeams;
+public:
+ CTFGCServerSystem( void );
+ ~CTFGCServerSystem( void );
+
+ // CAutoGameSystemPerFrame
+ virtual bool Init() OVERRIDE;
+ virtual void LevelInitPreEntity() OVERRIDE;
+ virtual void LevelShutdownPostEntity() OVERRIDE;
+ virtual void Shutdown() OVERRIDE;
+ virtual void PreClientUpdate() OVERRIDE;
+
+// uint8 FindItemID( CTF_Item *pItem );
+ void MatchSignOut();
+// const char *GetMatchStartTimeString();
+
+// void GameRules_State_Enter( DOTA_GameState newState );
+
+ void SetHibernation( bool bHibernating );
+ bool ShouldHideServer();
+
+ // ISharedObjectListener
+ virtual void SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE;
+ virtual void PreSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { /* do nothing */ }
+ virtual void SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE;
+ virtual void PostSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { /* do nothing */ }
+ virtual void SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE;
+ virtual void SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { }
+ virtual void SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { }
+
+ void DumpLobby();
+
+ // IServerGCLobby methods
+ virtual bool HasLobby() const;
+ virtual bool SteamIDAllowedToConnect(const CSteamID &steamId) const;
+ virtual void UpdateServerDetails(void);
+ virtual bool ShouldHibernate();
+
+ // IGameEventListener2
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+
+ CTFParty* GetPartyForPlayer( CSteamID steamID ) const;
+
+ CMatchInfo *GetMatch() { return m_pMatchInfo; }
+ const CMatchInfo *GetMatch() const { return m_pMatchInfo; }
+
+ // Verbose accessor helpers
+ //
+ // Get match only if it is live
+ CMatchInfo *GetLiveMatch() { return ( m_pMatchInfo && !m_pMatchInfo->m_bMatchEnded ) ? m_pMatchInfo : NULL; }
+ const CMatchInfo *GetLiveMatch() const { return const_cast<CTFGCServerSystem*>(this)->GetLiveMatch(); }
+ // Get a player only if there is a live match and they are still in the match (not dropped)
+ CMatchInfo::PlayerMatchData_t *GetLiveMatchPlayer( CSteamID steamID );
+ const CMatchInfo::PlayerMatchData_t *GetLiveMatchPlayer( CSteamID steamID ) const ;
+
+ int GetTeamForLobbyMember( const CSteamID &steamId ) const;
+// bool IsLobbyMemberBroadcaster( const CSteamID &steamId ) const;
+// ELanguage GetBroadcasterLanguage( const CSteamID &steamId ) const;
+ float GetFirstConnectTimeForLobbyMember( const CSteamID &steamId ) const;
+ int GetVoteKickAttemptsByLobbyMember( const CSteamID &steamID ) const;
+ void IncrementVoteKickAttemptsByLobbyMember( const CSteamID &steamID );
+//
+// EDOTA_Uploading_Match_Stats UploadingMatchStats() { return m_nUploadingMatchStats; }
+// void OnStatsSubmitted( uint32 unMatchID, int32 nReplaySalt );
+// uint32 GetLastMatchID() { return m_unLastMatchID; }
+// int32 GetLastReplaySalt() { return m_nLastReplaySalt; }
+
+ void ClientActive( CSteamID steamIDClient );
+ void ClientConnected( CSteamID steamIDPlayer, edict_t *pEntity );
+ void ClientDisconnected( CSteamID steamIDClient );
+
+ inline bool IsMMServerModeActive() const { return m_bMMServerMode; }
+
+ void MMServerModeChanged();
+
+// void SetRelayedGameServerSteamID( const CSteamID &steamID ) { m_relayedGameServerSteamID = steamID; }
+// void SetParentRelayCount( int nParentRelayCount ) { m_nParentRelayCount = nParentRelayCount; }
+
+ float GetTimeLastConnectedToGC( void ) { return m_timeLastConnectedToGC; }
+
+ void EndManagedMatch( bool bKickPlayersToParties = false );
+
+ // Sends match results. Expects the managed match be ended.
+
+ /// MvM game rules processing lets us the players have won
+ void SendMvMVictoryResult();
+
+ // Takes ownership of matchResultMsg
+ void SendCompetitiveMatchResult( GCSDK::CProtoBufMsg< CMsgGC_Match_Result > *matchResultMsg );
+
+ // If the GC has confirmed we are in the pool for late joins. GetTimeRequestedLateJoin() can be compared with
+ // BLateJoinEligible() to reason about delays in the GC making us available for late join.
+ bool BLateJoinEligible();
+
+ double GetTimeRequestedLateJoin() { return m_flTimeRequestedLateJoin; }
+
+ // Eject a player from the match, kicking them if they are still present, with given reason.
+ bool EjectMatchPlayer( CSteamID steamID, TFMatchLeaveReason eReason );
+
+ void MatchPlayerVoteKicked( CSteamID steamID );
+
+ const MapDef_t* GetNextMapVoteByIndex( int nIndex ) const;
+
+ // Changing Match Player Teams
+ //
+ // Is game logic allowed to perform team reassignments for this match mode?
+ bool CanChangeMatchPlayerTeams();
+ // When game logic changes the team of a match player, this should be called immediately. It is invalid to call this
+ // if CanChangeMatchPlayerTeams is false.
+ void ChangeMatchPlayerTeam( CSteamID steamID, TF_GC_TEAM eTeam );
+ // Multi-player version of the above, less GC traffic when multiple reassignments occur at once.
+ struct PlayerTeamPair_t { CSteamID steamID; TF_GC_TEAM eTeam; };
+ template< typename ANY_ALLOCATOR >
+ void ChangeMatchPlayerTeams( const CUtlVector< PlayerTeamPair_t, ANY_ALLOCATOR > &vecNewTeams );
+
+ // Rolling Matches
+ //
+ // Some match types let us keep a lobby and roll into a new match. This may not always be allowed depending on GC
+ // state.
+ //
+ // Upon calling RequestNewMatchForLobby, a timer starts, after which the new match is launched in
+ // LaunchNewMatchForLobby. During this period our match object is the old match, but the lobby may have been updated
+ // to reflect the new match.
+ //
+ // !! Currently if the GC is out of contact, we will speculatively continue with a new match. There are rare cases
+ // where the GC will return and decline this request, in which case the resulting match will be unofficial and
+ // not recorded. It should be trivial to add a bool to prevent such speculative matches should it be necessary.
+ bool CanRequestNewMatchForLobby();
+ void RequestNewMatchForLobby( const MapDef_t* pNewMap );
+
+ // If the match is in a bogus state and has no useful resolution, terminate it and submit a minidump. This usually
+ // just means reboot the match server.
+ void AbortInvalidMatchState();
+
+protected:
+
+ // CGCClientSystem
+ virtual void PreInitGC() OVERRIDE;
+ virtual void PostInitGC() OVERRIDE;
+
+private:
+ void SendPlayerLeftMatch( CSteamID steamID, TFMatchLeaveReason eReason, bool bAbandoned );
+
+ // Send a kick-lobby message for a stale or unexpected lobby
+ void SendRejectLobby();
+
+ // Kick a player that is no longer present in the match and should not be here.
+ // Returns true if they were present
+ bool KickRemovedMatchPlayer( CSteamID steamIDClient );
+
+ // The lobby object should only be looked at by this class and may be out of date when the GC reboots and similar --
+ // most users should use the canonical match state in GetMatch()
+ const CTFGSLobby *GetLobby() const;
+ CTFGSLobby *GetLobby();
+
+ // Accepts a reservation request from the GC, adding this player to our reserved list, and, for MM mode, to the
+ // match.
+ void AcceptGCReservation( CSteamID steamID, const CTFLobbyMember *pMemberData, bool bIsLateJoin, int nEntindex, bool bActive );
+
+ // Rolling Matches (private)
+ //
+ // If we requested a new match for our existing lobby. We don't actually launch the new match for this timer, but
+ // the GC may get back to us before (or after!) that period, so this tracks that we're not currently in sync with
+ // our lobby object. See RequestNewMatchForLobby / LaunchNewMatchForLobby and the "Team Assignments" section of the
+ // big comment at the top of tf_gc_server.cpp
+ bool BPendingNewMatch() const { return m_flWaitingForNewMatchTime != 0.f; }
+ // Called at the end of the m_flWaitingForNewMatchTime period to actually create the new match. We could let the
+ // caller finish the launch process by changing this timer to a bool and making this public.
+ void LaunchNewMatchForLobby();
+
+ // Callbacks from the GC
+ void ChangeMatchPlayerTeamsResponse( bool bSuccess );
+ void NewMatchForLobbyResponse( bool bSuccess );
+ // Static callbacks that are just forwarding to us
+ static void ChangeMatchPlayerTeamsResponseCallback( GCSDK::CProtoBufMsg<CMsgGCChangeMatchPlayerTeamsResponse>& msg );
+ static void NewMatchForLobbyResponseCallback( GCSDK::CProtoBufMsg<CMsgGCNewMatchForLobbyResponse>& msg );
+
+ bool m_bSetupSchema;
+
+ RTime32 m_unGameStartTime;
+ float m_timeLastSendGameServerInfoAndConnectedPlayers;
+ ServerMatchmakingState m_eLastGameServerUpdateState;
+ TF_MatchmakingMode m_eLastGameServerUpdateMatchmakingMode;
+ CUtlString m_sLastGameServerUpdateMap;
+ CUtlString m_sLastGameServerUpdateTags;
+ int m_nLastGameServerUpdateBotCount;
+ int m_nLastGameServerUpdateMaxHumans;
+ int m_nLastGameServerUpdateSlotsFree;
+ uint32 m_nLastGameServerUpdateLobbyMMVersion;
+
+// EDOTA_Uploading_Match_Stats m_nUploadingMatchStats;
+// uint32 m_unLastMatchID;
+// int32 m_nLastReplaySalt;
+ CSteamID m_ourSteamID;
+ CSteamID m_relayedGameServerSteamID;
+ int m_nParentRelayCount;
+ bool m_bMMServerMode;
+ double m_flTimeBecameEmptyWithLobby;
+ double m_flTimeRequestedLateJoin;
+ bool m_bLateJoinEligible;
+ int m_iSavedVisibleMaxPlayers;
+ bool m_bOverridingVisibleMaxPlayers;
+ bool m_bWaitingForNewMatchID;
+ float m_flWaitingForNewMatchTime;
+
+ CMvMVictoryInfo m_mvmVictoryInfo;
+
+ // Check for match players who have been disconnected for long enough to warrant an abandon and do so.
+ void MatchPlayerAbandonThink();
+
+ void SetMatchPlayerDropped( CSteamID steamID, TFMatchLeaveReason eReason );
+
+ void UpdateConnectedPlayersAndServerInfo( CMsgGameServerMatchmakingStatus_Event event, bool bForceSendServerInfo );
+
+ CMatchInfo *m_pMatchInfo;
+ float m_timeLastConnectedToGC;
+
+// DOTAGameVersion m_GameVersion;
+};
+
+CTFGCServerSystem *GTFGCClientSystem();
+
+#endif // #ifdef ENABLE_GC_MATCHMAKING
+
+#endif // TF_GC_SERVER_H