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.
206 lines
8.3 KiB
206 lines
8.3 KiB
![]()
4 years ago
|
//========= 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;
|
||
|
}
|