1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
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
|