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.
205 lines
8.3 KiB
205 lines
8.3 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
#include "tf_quickplay_shared.h" |
|
|
|
// |
|
// NOTE: This actually declares global variables and is intended to |
|
// only be included ONCE on the client, and ONCE on the GC. |
|
// |
|
|
|
#ifdef GC |
|
#define TF2SCORECONVAR(name, defaultval, desc) GCConVar name(#name, defaultval, FCVAR_REPLICATED, desc) |
|
#else |
|
#define TF2SCORECONVAR(name, defaultval, desc) ConVar name(#name, defaultval, FCVAR_NONE, desc) |
|
#endif |
|
|
|
TF2SCORECONVAR(tf_matchmaking_numbers_serverfull_headroom, "1", "Scoring will consider the server 'full' when this many slots are available" ); |
|
TF2SCORECONVAR(tf_matchmaking_numbers_valve_bonus_hrs_a, "8.00", "Valve server scoring bonus: hours played A" ); |
|
TF2SCORECONVAR(tf_matchmaking_numbers_valve_bonus_pts_a, "0.30", "Valve server scoring bonus: bonus points A" ); |
|
TF2SCORECONVAR(tf_matchmaking_numbers_valve_bonus_hrs_b, "16.00", "Valve server scoring bonus: hours played B" ); |
|
TF2SCORECONVAR(tf_matchmaking_numbers_valve_bonus_pts_b, "0.00", "Valve server scoring bonus: bonus points B" ); |
|
TF2SCORECONVAR(tf_matchmaking_numbers_increase_maxplayers_penalty, "0.50", "Max scoring penalty to servers that have increased the max number of players" ); |
|
TF2SCORECONVAR(tf_matchmaking_retry_cooldown_seconds, "300", "Time to remember quickplay join attempt, and apply scoring penalty to rejoin the same server" ); |
|
TF2SCORECONVAR(tf_matchmaking_retry_max_penalty, "1.0", "Max scoring penalty to rejoin a server previously matched. (Decays linearly over the cooldown period)" ); |
|
TF2SCORECONVAR(tf_matchmaking_noob_map_score_boost, "0.75", "Boost added for quick-plaay scoring purposes if you are a noob and the map is considered noob-friendly" ); |
|
TF2SCORECONVAR(tf_matchmaking_noob_hours_played, "8.0", "Number of hours played to determine 'noob' status for quickplay scoring purposes" ); |
|
|
|
TF2SCORECONVAR(tf_matchmaking_ping_a, "50.0f", "Quickplay scoring ping time data point A" ); |
|
TF2SCORECONVAR(tf_matchmaking_ping_a_score, "0.9", "Quickplay scoring ping score data point A" ); |
|
TF2SCORECONVAR(tf_matchmaking_ping_b, "150.0f", "Quickplay scoring ping time data point B" ); |
|
TF2SCORECONVAR(tf_matchmaking_ping_b_score, "0.0", "Quickplay scoring ping score data point B" ); |
|
TF2SCORECONVAR(tf_matchmaking_ping_c, "300.0f", "Quickplay scoring ping time data point C" ); |
|
TF2SCORECONVAR(tf_matchmaking_ping_c_score, "-1.0", "Quickplay scoring ping score data point C" ); |
|
|
|
TF2SCORECONVAR(tf_matchmaking_goodenough_score_start, "8.5", "Good enough score at start of search" ); |
|
TF2SCORECONVAR(tf_matchmaking_goodenough_count_start, "20", "Good enough count at start of search" ); |
|
TF2SCORECONVAR(tf_matchmaking_goodenough_score_end, "7.0", "Good enough score at end of search" ); |
|
TF2SCORECONVAR(tf_matchmaking_goodenough_count_end, "5", "Good enough count at end of search" ); |
|
|
|
TF2SCORECONVAR( tf_mm_options_bonus, "0.5", "Scoring bonus when approaching tobor rating." ); |
|
TF2SCORECONVAR( tf_mm_options_penalty, "-0.25", "Scoring penalty when options score is too far outside an acceptable range." ); |
|
|
|
TF2SCORECONVAR( tf_matchmaking_server_player_count_score, "1.5", "Maximum score when server is at/near optimal player count." ); |
|
|
|
//ConVar tf_matchmaking_goodenough_hi_score_start( "tf_matchmaking_goodenough_hi_score_start", "6.0", FCVAR_NONE ); |
|
//ConVar tf_matchmaking_goodenough_hi_count_start( "tf_matchmaking_goodenough_hi_count_start", "5", FCVAR_NONE ); |
|
//ConVar tf_matchmaking_goodenough_hi_score_end( "tf_matchmaking_goodenough_hi_score_end", "3.0", FCVAR_NONE ); |
|
//ConVar tf_matchmaking_goodenough_hi_count_end( "tf_matchmaking_goodenough_hi_count_end", "2", FCVAR_NONE ); |
|
|
|
ConVar tf_matchmaking_max_search_time( "tf_matchmaking_max_search_time", "45", FCVAR_NONE ); |
|
|
|
#undef TF2SCORECONVAR |
|
|
|
static inline float lerp( float inA, float outA, float inB, float outB, float x ) |
|
{ |
|
Assert( inA != inB ); |
|
return outA + ( outB - outA ) * ( x - inA ) / ( inB - inA ); |
|
} |
|
|
|
struct TF2ScoringNumbers_t |
|
{ |
|
|
|
// |
|
// If we do further experiments, we should make distinct enum values, so we can easily |
|
// compare the stats on the backend |
|
// |
|
enum ExperimentGroup_t |
|
{ |
|
k_ExperimentGroup_None, // no experiment active |
|
|
|
// |
|
// Experiment 1 |
|
// |
|
k_ExperimentGroup_Experiment1_Control = 1, |
|
k_ExperimentGroup_Experiment1_ValveBias = 2, |
|
k_ExperimentGroup_Experiment1_ValveBiasInactive = 3, |
|
k_ExperimentGroup_Experiment1_CommunityBias = 4, |
|
k_ExperimentGroup_Experiment1_CommunityBiasInactive = 5, |
|
}; |
|
|
|
ExperimentGroup_t m_eExperimentGroup; |
|
|
|
TF2ScoringNumbers_t( CSteamID whosAsking ) |
|
{ |
|
m_eExperimentGroup = k_ExperimentGroup_None; |
|
|
|
// |
|
// Assign experiment group for experiment 1 |
|
// |
|
switch ( whosAsking.GetAccountID() & 3 ) |
|
{ |
|
case 0: |
|
// 80% chance to adjust behaviour |
|
if ( RandomFloat( 0.0f, 1.0f) < 0.8f ) |
|
{ |
|
m_eExperimentGroup = k_ExperimentGroup_Experiment1_ValveBias; |
|
} |
|
else |
|
{ |
|
m_eExperimentGroup = k_ExperimentGroup_Experiment1_ValveBiasInactive; |
|
} |
|
break; |
|
|
|
case 2: |
|
// 80% chance to adjust behaviour |
|
if ( RandomFloat( 0.0f, 1.0f) < 0.8f ) |
|
{ |
|
m_eExperimentGroup = k_ExperimentGroup_Experiment1_CommunityBias; |
|
} |
|
else |
|
{ |
|
m_eExperimentGroup = k_ExperimentGroup_Experiment1_CommunityBiasInactive; |
|
} |
|
break; |
|
|
|
default: |
|
m_eExperimentGroup = k_ExperimentGroup_Experiment1_Control; |
|
break; |
|
} |
|
} |
|
}; |
|
|
|
float QuickplayCalculateServerScore( int numHumans, int numBots, int maxPlayers, int nNumInSearchParty ) |
|
{ |
|
Assert( nNumInSearchParty > 0 ); |
|
|
|
// Safety check against a degenerate case with invalid max number of players. |
|
// Protects against some bad math below |
|
if ( maxPlayers < kTFQuickPlayMinMaxNumberOfPlayers ) |
|
{ |
|
return -100.0f; |
|
} |
|
if ( maxPlayers > kTFQuickPlayMaxPlayers ) |
|
{ |
|
// Server should have been filtered, but in case we get here... |
|
maxPlayers = kTFQuickPlayMaxPlayers; |
|
} |
|
|
|
float score = 0.0; |
|
|
|
// Check for completely full server |
|
int newNumHumans = numHumans + nNumInSearchParty; |
|
int newNumTotalPlayers = newNumHumans + numBots; |
|
if ( newNumTotalPlayers + tf_matchmaking_numbers_serverfull_headroom.GetInt() > maxPlayers ) |
|
{ |
|
// Server full! Huge penalty! |
|
score += -100.0f; |
|
} |
|
else |
|
{ |
|
// Data points for piecewise linear interpolation. |
|
// First point is implied: empty server is a score of zero. |
|
// |
|
// Then we increase up to point A |
|
int playerCountA = maxPlayers / 3; |
|
float scoreA = 0.20f; |
|
|
|
// Next data point is when the score peaks at 100% |
|
int idealPlayerCount = maxPlayers * 5 / 6; |
|
|
|
// Finally, the last data point when the server is full. |
|
// This choice reflects a pretty steep dropoff. This server |
|
// is already in a good state, we should begin to send players |
|
// to other servers so that they can start to fill up, and reduce |
|
// the race condition with players trying to join nearly full |
|
// servers and being too late and getting rejected. |
|
float scoreFull = scoreA; |
|
float flMaxScore = Max( tf_matchmaking_server_player_count_score.GetFloat(), 0.1f ); |
|
|
|
// Do the piecewise linear interpolation |
|
if ( newNumHumans <= playerCountA ) |
|
{ |
|
score += lerp( 0, 0.0f, playerCountA, scoreA, float( newNumHumans ) ); |
|
} |
|
else if ( newNumHumans <= idealPlayerCount ) |
|
{ |
|
// Interpolate from point A up to 100% |
|
score += lerp( float( playerCountA ), scoreA, idealPlayerCount, flMaxScore, float( newNumHumans ) ); |
|
} |
|
else |
|
{ |
|
// Greater than ideal. Interpolate back down to the score when the server is full |
|
score += lerp( idealPlayerCount, flMaxScore, maxPlayers, scoreFull, float( newNumHumans ) ); |
|
} |
|
} |
|
|
|
// Don't apply a penalty anymore. Instead, we just let players express their preference |
|
// // Give a penalty for servers that increase the max player |
|
// // number above the ideal. |
|
// if ( maxPlayers > kTFQuickPlayIdealMaxNumberOfPlayers ) |
|
// { |
|
// // Max penalty, if they increased it up all the way to |
|
// // kTFQuickPlayMaxPlayers. (Above this, we reject them completely) |
|
// int nExcessPlayers = maxPlayers - kTFQuickPlayIdealMaxNumberOfPlayers; |
|
// const int kMaxExcessPlayers = kTFQuickPlayMaxPlayers - kTFQuickPlayIdealMaxNumberOfPlayers; |
|
// float penalty = tf_matchmaking_numbers_increase_maxplayers_penalty.GetFloat() * (float)nExcessPlayers / (float)kMaxExcessPlayers; |
|
// score -= penalty; |
|
// } |
|
|
|
//// being tagged as quickplay is roughly the same weight as best ping and best ratio of player numbers |
|
//if ( bHasQuickplayTag ) |
|
//{ |
|
// item.score += 2.0f; |
|
//} |
|
|
|
return score; |
|
}
|
|
|