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.
351 lines
13 KiB
351 lines
13 KiB
//========= 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
|
|
|