diff options
Diffstat (limited to 'game/shared/tf/tf_matchmaking_shared.h')
| -rw-r--r-- | game/shared/tf/tf_matchmaking_shared.h | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/game/shared/tf/tf_matchmaking_shared.h b/game/shared/tf/tf_matchmaking_shared.h new file mode 100644 index 0000000..75394a6 --- /dev/null +++ b/game/shared/tf/tf_matchmaking_shared.h @@ -0,0 +1,351 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Matchmaking stuff shared between GC and gameserver / client +// +//============================================================================= + +#ifndef TF_MATCHMAKING_SHARED_H +#define TF_MATCHMAKING_SHARED_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_gcmessages.pb.h" + +class IMatchGroupDescription; + +#define NEXT_MAP_VOTE_OPTIONS 3 + +// Replace this hard-coded value concept in order to support all bracket types (i.e. anything higher than 6v6, etc) +// This increases a number of hard-coded data structures and so on, so beware +#define MATCH_SIZE_MAX 24 + +// Similarly, some hot path MM structures use this for fixed arrays. It should be the maximum number of players that +// will ever be in a party and no larger. +#define MAX_PARTY_SIZE 6 + +// Range clients are allowed to pass up for custom ping tolerance +// Currently matches CS:GO +#define CUSTOM_PING_TOLERANCE_MIN 25 +#define CUSTOM_PING_TOLERANCE_MAX 350 + +// XXX(JohnS): Before we can actually use other rating backends for matchmaking or display purposes, there are remaining +// hard coded assumptions about where the primary rating is, and issues with e.g. Match_Result assuming the +// backend in use *now* is what the match was created for, etc. I've sprinkled this around at all the +// landmines I found while implementing the new rating backend, so... comment this out and fix everything +// that breaks. +static inline void FixmeMMRatingBackendSwapping() {} + +// Backend-agnostic storage type for rating data, so we're not manually passing pairs around to every rating-specific +// interface when we inevitably decide to add a third. +// +// Keep in mind that it is much easier to query well structured data for reports & otherwise, so we should prefer +// e.g. adding another value to packing two 16-bit ints in RatingSecondary for a new backend that needs more 16-bit +// values. (The calculus may change if we end up with a backend that has eight 8-bit values, however) +// +// Schema objects that embed rating data: RatingHistory, RatingData +// Proto objects that embed rating data: CSOTFRatingData +struct MMRatingData_t { + uint32_t unRatingPrimary; + uint32_t unRatingSecondary; + uint32_t unRatingTertiary; + + inline bool operator==(const MMRatingData_t &b) const + { return this->unRatingPrimary == b.unRatingPrimary && + this->unRatingSecondary == b.unRatingSecondary && + this->unRatingTertiary == b.unRatingTertiary; } +}; + +// Stored value, don't re-order +enum EMatchGroup +{ + k_nMatchGroup_Invalid = -1, + k_nMatchGroup_First = 0, + + k_nMatchGroup_MvM_Practice = 0, + k_nMatchGroup_MvM_MannUp, + + k_nMatchGroup_Ladder_6v6, + k_nMatchGroup_Ladder_9v9, + k_nMatchGroup_Ladder_12v12, + + k_nMatchGroup_Casual_6v6, + k_nMatchGroup_Casual_9v9, + k_nMatchGroup_Casual_12v12, + + k_nMatchGroup_Count, + // When adding a new matchgroup, add case handling to GetMatchSizeForMatchGroup(), GetMatchGroupName(), GetServerPoolName(), GetMaxLobbySizeForMatchGroup(), YldWebAPIServersByDataCenter() +}; + +// Stored value, don't re-order +// +// If you add a new backend, see ITFMMRatingBackend::GetRatingBackend -- you need to at least provide a GetDefault() +enum EMMRating +{ + k_nMMRating_LowestValue = -1, + k_nMMRating_Invalid = -1, + + k_nMMRating_First = 0, + k_nMMRating_6v6_DRILLO = 0, + k_nMMRating_6v6_DRILLO_PlayerAcknowledged = 1, + k_nMMRating_6v6_GLICKO = 2, + k_nMMRating_12v12_DRILLO = 3, + k_nMMRating_12v12_GLICKO = 4, + k_nMMRating_Last = 4, +}; + +// This must be in the range of an int16 for database serialization +COMPILE_TIME_ASSERT( k_nMMRating_LowestValue >= INT16_MIN ); +COMPILE_TIME_ASSERT( k_nMMRating_Last <= INT16_MAX ); + +// Stored value, don't re-order +enum EMMRatingSource +{ + k_nMMRatingSource_LowestValue = -1, + k_nMMRatingSource_Invalid = -1, + + k_nMMRatingSource_Match = 0, // Match result. Source ID is match ID + k_nMMRatingSource_Admin = 1, // Admin command/manual adjustment. Source ID is probably 0 or something. + k_nMMRatingSource_PlayerAcknowledge = 2, // For 'acknowledge' type ratings, the player acknowledged the value + k_nMMRatingSource_ImportedOldSystem = 3, // For pre-history ratings, this source is used once for 'initial rating from old system' + k_nMMRatingSource_Last = 3, +}; + +// This must be in the range of an int16 for database serialization +COMPILE_TIME_ASSERT( k_nMMRatingSource_LowestValue >= INT16_MIN ); +COMPILE_TIME_ASSERT( k_nMMRatingSource_Last <= INT16_MAX ); + +// Also update this guy if you do the thing +const char *GetMatchGroupName( EMatchGroup eMatchGroup ); + +// Probably a better place for this... +enum ELadderLeaderboardTypes +{ + LADDER_LEADERBOARDS_6V6 = 0, + LADDER_LEADERBOARDS_PUBLIC, + LADDER_LEADERBOARDS_9V9, + LADDER_LEADERBOARDS_12V12, + LADDER_LEADERBOARDS_MAX +}; + +// Late join modes +enum EMatchMode +{ + // Uninitialized/unknown + eMatchMode_Invalid, + // Not late join / don't use late join + eMatchMode_MatchMaker_CompleteFromQueue, + // The add-one-player-at-a-time mode that doesn't work with the new scoring system, but still used for MvM and other + // old-scoring-system stuff. + eMatchMode_MatchMaker_LateJoinDropIn, + // The new late join mode that re-evaulates complete matches with the missing spot(s) filled. + eMatchMode_MatchMaker_LateJoinMatchBased, + // A match that is being manually crafted + eMatchMode_Manual, +}; + +const EMatchGroup k_nMatchGroup_Ladder_First = k_nMatchGroup_Ladder_6v6; +const EMatchGroup k_nMatchGroup_Ladder_Last = k_nMatchGroup_Ladder_12v12; + +const EMatchGroup k_nMatchGroup_Casual_First = k_nMatchGroup_Casual_6v6; +const EMatchGroup k_nMatchGroup_Casual_Last = k_nMatchGroup_Casual_12v12; + +inline bool IsMvMMatchGroup( EMatchGroup eMatchGroup ) +{ + return ( eMatchGroup == k_nMatchGroup_MvM_Practice ) || ( eMatchGroup == k_nMatchGroup_MvM_MannUp ); +} + +inline bool IsLadderGroup( EMatchGroup eMatchGroup ) +{ + return ( eMatchGroup >= k_nMatchGroup_Ladder_First && eMatchGroup <= k_nMatchGroup_Ladder_Last ) + || ( eMatchGroup >= k_nMatchGroup_Casual_First && eMatchGroup <= k_nMatchGroup_Casual_Last ); +} + +inline bool IsCasualGroup( EMatchGroup eMatchGroup ) +{ + return ( eMatchGroup >= k_nMatchGroup_Casual_First ) && ( eMatchGroup <= k_nMatchGroup_Casual_Last ); +} + +inline bool IsMannUpGroup( EMatchGroup eMatchGroup ) +{ + switch ( eMatchGroup ) + { + case k_nMatchGroup_MvM_Practice: + return false; + case k_nMatchGroup_MvM_MannUp: + return true; + case k_nMatchGroup_Ladder_6v6: + case k_nMatchGroup_Ladder_9v9: + case k_nMatchGroup_Ladder_12v12: + return false; + case k_nMatchGroup_Invalid: + default: + Assert( !"IsMannUpGroup called with invalid match group" ); + return false; + } + + return false; +} + +enum EMMServerMode +{ + eMMServerMode_Idle, + eMMServerMode_Incomplete_Match, + eMMServerMode_Full, + eMMServerMode_Count +}; + +// Separate penalty pools (and rules) for different classes of modes +enum EMMPenaltyPool +{ + eMMPenaltyPool_Invalid, + eMMPenaltyPool_Casual, // Pool with lenient penalties for most casual/mainstream gamemodes + eMMPenaltyPool_Ranked // Pool with strict and cumulative penalties for ranked gamemodes where abandons tank matches +}; + +enum +{ + // !! This should match up with GetServerPoolIndex to map these between match groups and server pools + // eMMServerMode_NotParticipating + k_nGameServerPool_NotParticipating = -1, + + // eMMServerMode_Incomplete_Match + k_nGameServerPool_MvM_Practice_Incomplete_Match = 0, + k_nGameServerPool_MvM_MannUp_Incomplete_Match, + k_nGameServerPool_Ladder_6v6_Incomplete_Match, + k_nGameServerPool_Ladder_9v9_Incomplete_Match, + k_nGameServerPool_Ladder_12v12_Incomplete_Match, + k_nGameServerPool_Casual_6v6_Incomplete_Match, + k_nGameServerPool_Casual_9v9_Incomplete_Match, + k_nGameServerPool_Casual_12v12_Incomplete_Match, + + // eMMServerMode_Full + k_nGameServerPool_MvM_Practice_Full, + k_nGameServerPool_MvM_MannUp_Full, + k_nGameServerPool_Ladder_6v6_Full, + k_nGameServerPool_Ladder_9v9_Full, + k_nGameServerPool_Ladder_12v12_Full, + k_nGameServerPool_Casual_6v6_Full, + k_nGameServerPool_Casual_9v9_Full, + k_nGameServerPool_Casual_12v12_Full, + + // eMMServerMode_Idle + k_nGameServerPool_Idle, + // When adding a new matchgroup, add case handling to GetMatchSizeForMatchGroup(), GetMatchGroupName(), GetServerPoolName(), GetMaxLobbySizeForMatchGroup(), YldWebAPIServersByDataCenter() + + k_nGameServerPoolCountTotal, +}; + +// Also update this guy if you touch pools +const char *GetServerPoolName( int iServerPool ); + +const int k_nGameServerPool_Incomplete_Match_First = k_nGameServerPool_MvM_Practice_Incomplete_Match; +const int k_nGameServerPool_Incomplete_Match_Last = k_nGameServerPool_Casual_12v12_Incomplete_Match; +const int k_nGameServerPool_Full_First = k_nGameServerPool_MvM_Practice_Full; +const int k_nGameServerPool_Full_Last = k_nGameServerPool_Casual_12v12_Full; + +COMPILE_TIME_ASSERT( k_nGameServerPool_Incomplete_Match_First + k_nMatchGroup_Count - 1 == k_nGameServerPool_Incomplete_Match_Last ); +COMPILE_TIME_ASSERT( k_nGameServerPool_Full_First + k_nMatchGroup_Count - 1 == k_nGameServerPool_Full_Last ); + +inline bool IsIncompleteMatchPool( int nGameServerPool ) +{ + return nGameServerPool >= k_nGameServerPool_Incomplete_Match_First && nGameServerPool <= k_nGameServerPool_Incomplete_Match_Last; +} + +// Stuff is simpler if we can set a max number of challenges in the schema +#define MAX_MVM_CHALLENGES 64 + +// Store a set of MvM challenges (search criteria, etc) +class CMvMMissionSet +{ +public: + CMvMMissionSet(); + CMvMMissionSet( const CMvMMissionSet &x ); + ~CMvMMissionSet(); + void operator=( const CMvMMissionSet &x ); + bool operator==( const CMvMMissionSet &x ) const; + + /// Set to the empty set + void Clear(); + + /// get/set individual bits, based on index of the challenge in the schema + void SetMissionBySchemaIndex( int iChallengeSchemaIndex, bool flag ); + bool GetMissionBySchemaIndex( int iChallengeSchemaIndex ) const; + + /// Intersect this set with the other set. Use IsEmpty() + /// to see if this produced the empty set. + void Intersect( const CMvMMissionSet &x ); + + /// Return true if the two sets have a nonzero intersection. (Neither object is modified) + bool HasIntersection( const CMvMMissionSet &x ) const; + + /// Return true if any challenges are selected + bool IsEmpty() const; +private: + + COMPILE_TIME_ASSERT( MAX_MVM_CHALLENGES <= 64 ); + + // Just use a plain old uint64 for now. We can make this into a proper bitfield class at some point + uint64 m_bits; +}; + +// Player Skill Ratings +const int k_nDrilloRating_MinRatingAdjust = 1; +const int k_nDrilloRating_MaxRatingAdjust = 100; +const int k_nDrilloRating_Ladder_MaxRatingAdjust = 500; +const int k_nDrilloRating_Ladder_MaxLossAdjust_LowRank = 100; +const uint32 k_unDrilloRating_MaxDifference = 25000; +const uint32 k_unDrilloRating_Min = 1; +const uint32 k_unDrilloRating_Ladder_Min = 10000; +const uint32 k_unDrilloRating_Max = 50000; +const uint32 k_unDrilloRating_Avg = 20000; +const uint32 k_unDrilloRating_Ladder_Start = 10000; +const uint32 k_unDrilloRating_Ladder_LowSkill = 19500; // First 6 ranks ceiling +const uint32 k_unDrilloRating_Ladder_HighSkill = 33001; // Last 6 ranks floor + +struct MapDef_t; + +//----------------------------------------------------------------------------- +// Purpose: Wrapper class to make dealing with CMsgCasualMatchmakingSearchCriteria +// much easier. +//----------------------------------------------------------------------------- +class CCasualCriteriaHelper +{ +public: + CCasualCriteriaHelper( const CMsgCasualMatchmakingSearchCriteria& criteria ); + + bool IsMapSelected( const MapDef_t* pMapDef ) const; + bool IsMapSelected( const uint32 nMapDefIndex ) const; + bool IsValid() const; + bool AnySelected() const { return !m_mapsBits.IsAllClear(); } + CMsgCasualMatchmakingSearchCriteria GetCasualCriteria() const; + + void Intersect( const CMsgCasualMatchmakingSearchCriteria& otherCriteria ); + bool SetMapSelected( uint32 nMapDefIndex, bool bSelected ); + + void Clear( void ); + +private: + bool IsMapInValidCategory( uint32 nMapDefIndex ) const; + +private: + CLargeVarBitVec m_mapsBits; +}; + +// CSOTFLobby flags +#define LOBBY_FLAG_LOWPRIORITY ( 1 << 0 ) +#define LOBBY_FLAG_REMATCH ( 1 << 1 ) + +// CMsgGC_Match_Result match flags +#define MATCH_FLAG_LOWPRIORITY ( 1 << 0 ) +#define MATCH_FLAG_REMATCH ( 1 << 1 ) + +// CMsgGC_Match_Result player flags +#define MATCH_FLAG_PLAYER_LEAVER ( 1 << 0 ) +#define MATCH_FLAG_PLAYER_LATEJOIN ( 1 << 1 ) +// Separate from LEAVER - was marked as an abandon and issued a penalty. You can be a leaver without being an +// abandoner. +#define MATCH_FLAG_PLAYER_ABANDONER ( 1 << 2 ) +#define MATCH_FLAG_PLAYER_PLAYED ( 1 << 3 ) // Did they stay long enough for the game to start? + +#endif // #ifndef TF_MATCHMAKING_SHARED_H |