Team Fortress 2 Source Code as on 22/4/2020
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.

1783 lines
66 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "tf_match_description.h"
  8. #include "tf_ladder_data.h"
  9. #include "tf_rating_data.h"
  10. #ifdef GC_DLL
  11. #include "tf_lobby.h"
  12. #include "tf_partymanager.h"
  13. #endif
  14. #if defined CLIENT_DLL || defined GAME_DLL
  15. #include "tf_gamerules.h"
  16. #endif
  17. #ifdef CLIENT_DLL
  18. #include "tf_gc_client.h"
  19. #include "animation.h"
  20. #include "vgui/ISurface.h"
  21. #include "vgui_controls/Controls.h"
  22. #include "tf_lobby_server.h"
  23. #endif
  24. #ifdef GAME_DLL
  25. #include "tf_lobby_server.h"
  26. #include "tf_gc_server.h"
  27. #include "tf_objective_resource.h"
  28. #include "team_control_point_master.h"
  29. #endif
  30. #ifdef GC_DLL
  31. GCConVar tf_mm_ladder_force_map_by_name( "tf_mm_ladder_force_map_by_name", "", "If specified, force any matches that form for 6v6, 9v9, 12v12 to use the specified map (e.g. cp_sunshine)." );
  32. GCConVar tf_mm_casual_rejoin_cooldown_secs( "tf_mm_casual_rejoin_cooldown_secs", "180",
  33. "How many seconds must pass before anyone can re-match into a casual or MvM lobby they have left. "
  34. "Setting this too low may allow someone to rejoin a lobby while a vote-kick for them is still occuring, but before it has passed." );
  35. GCConVar tf_mm_casual_rejoin_cooldown_votekick_secs( "tf_mm_casual_rejoin_cooldown_votekick_secs", "10800", // 3 hours
  36. "How many seconds must pass before a vote-kicked player can re-match into a casual or MvM lobby. "
  37. "This is not infinite as casual lobbies may persist for days." );
  38. // These are in matchmaking shared
  39. extern GCConVar tf_mm_match_size_mvm;
  40. extern GCConVar tf_mm_match_size_ladder_6v6;
  41. extern GCConVar tf_mm_match_size_ladder_9v9;
  42. extern GCConVar tf_mm_match_size_ladder_12v12;
  43. extern GCConVar tf_mm_match_size_ladder_12v12_minimum;
  44. #else
  45. extern ConVar tf_mm_match_size_mvm;
  46. extern ConVar tf_mm_match_size_ladder_6v6;
  47. extern ConVar tf_mm_match_size_ladder_9v9;
  48. extern ConVar tf_mm_match_size_ladder_12v12;
  49. extern ConVar tf_mm_match_size_ladder_12v12_minimum;
  50. extern ConVar servercfgfile;
  51. extern ConVar lservercfgfile;
  52. extern ConVar mp_tournament_stopwatch;
  53. extern ConVar tf_gamemode_payload;
  54. extern ConVar tf_gamemode_ctf;
  55. #endif
  56. #ifdef GAME_DLL
  57. extern ConVar tf_mvm_allow_abandon_after_seconds;
  58. extern ConVar tf_mvm_allow_abandon_below_players;
  59. #endif
  60. #ifdef GC_DLL
  61. #define MVM_REQUIRED_SCORE &tf_mm_required_score_mvm
  62. #define LADDER_REQUIRED_SCORE &tf_mm_required_score_ladder
  63. #define CASUAL_REQUIRED_SCORE &tf_mm_required_score_ladder
  64. #else
  65. #define MVM_REQUIRED_SCORE (ConVar*)NULL
  66. #define LADDER_REQUIRED_SCORE (ConVar*)NULL
  67. #define CASUAL_REQUIRED_SCORE (ConVar*)NULL
  68. #endif
  69. #ifdef GC_DLL
  70. #include "tf_matchmaker.h"
  71. #include "tf_party.h"
  72. using namespace GCSDK;
  73. extern GCConVar tf_mm_scoring_ladder_skillrating_delta_max_high;
  74. extern GCConVar tf_mm_scoring_ladder_skillrating_delta_max;
  75. extern GCConVar tf_mm_required_score_mvm;
  76. extern GCConVar tf_mm_required_score_ladder;
  77. extern GCConVar tf_mm_required_score_pvp;
  78. // Predicate for below filters
  79. typedef std::function< bool(const CTFMemcachedLobbyFormerMember &) > FilterLeftMember_t;
  80. // Helper that finds any party members that appear in the given match's m_mapFormerMembersByAccountID as having left the
  81. // match, and calls the given predicate on them.
  82. //
  83. // Returns true if no party members are in the former member list with a match-leave flag, or they all pass the
  84. // predicate.
  85. //
  86. // Returns false and stops iterating if predicate fails.
  87. static bool BCheckLeftMatchMembersAgainstParty( const MatchDescription_t *pMatch,
  88. const MatchParty_t *pParty,
  89. const FilterLeftMember_t &predicate )
  90. {
  91. FOR_EACH_VEC( pParty->m_vecMembers, idxMember )
  92. {
  93. const MatchParty_t::Member_t &member = pParty->m_vecMembers[ idxMember ];
  94. UtlHashHandle_t idx = pMatch->m_mapFormerMembersByAccountID.Find( member.m_steamID.GetAccountID() );
  95. if ( pMatch->m_mapFormerMembersByAccountID.IsValidHandle( idx ) )
  96. {
  97. const CTFMemcachedLobbyFormerMember &formerMember = pMatch->m_mapFormerMembersByAccountID[ idx ];
  98. // Only care about former members that left a match this lobby was visiting
  99. if ( !formerMember.has_left_match_time() )
  100. { continue; }
  101. if ( !predicate( formerMember ) )
  102. {
  103. return false;
  104. }
  105. }
  106. }
  107. return true;
  108. }
  109. // Helper that finds any newly-added (not existing incomplete-match-party) members of the given match description that
  110. // appear in m_mapFormerMembersByAccountID as having left the match previously, and calls the given predicate on them.
  111. //
  112. // Returns true if no new match members appear in the former member list with a match-leave flag, or they all pass the
  113. // predicate.
  114. //
  115. // Returns false and stops iterating if predicate fails.
  116. static bool BCheckLeftMatchMembersAgainstNewlyAddedMembers( const MatchDescription_t *pMatch,
  117. const FilterLeftMember_t &predicate )
  118. {
  119. FOR_EACH_VEC( pMatch->m_vecParties, idx )
  120. {
  121. auto *pParty = pMatch->m_vecParties[ idx ];
  122. // If this party is not an incomplete match party (and thus already in the match), run the check
  123. if ( !pParty->m_bIncompleteMatchParty && !BCheckLeftMatchMembersAgainstParty( pMatch, pParty, predicate ) )
  124. {
  125. return false;
  126. }
  127. }
  128. return true;
  129. }
  130. // Shared helper for the casual modes to have a consistent rejoin policy
  131. static bool BThreadedFormerMemberMayRejoinJoinCasualOrMvMMatch( const CTFMemcachedLobbyFormerMember& member )
  132. {
  133. RTime32 rtNow = CRTime::RTime32TimeCur();
  134. RTime32 rtLeftLobby = member.left_lobby_time();
  135. uint32_t unRejoinTimeout = (uint32_t)Max( tf_mm_casual_rejoin_cooldown_secs.GetInt(), 0 );
  136. if ( unRejoinTimeout && rtLeftLobby != 0 &&
  137. rtLeftLobby + unRejoinTimeout > rtNow )
  138. {
  139. // Rejoin too soon after leaving lobby
  140. return false;
  141. }
  142. uint32_t unVoteKickTimeout = (uint32_t)Max( tf_mm_casual_rejoin_cooldown_votekick_secs.GetInt(), 0 );
  143. RTime32 rtLeftMatch = member.left_match_time();
  144. TFMatchLeaveReason eLeftMatchReason = member.left_match_reason();
  145. if ( unVoteKickTimeout &&
  146. rtLeftMatch != 0 &&
  147. ( eLeftMatchReason == TFMatchLeaveReason_VOTE_KICK ||
  148. eLeftMatchReason == TFMatchLeaveReason_ADMIN_KICK ) &&
  149. rtLeftMatch + unVoteKickTimeout > rtNow )
  150. {
  151. // Too soon after votekick
  152. return false;
  153. }
  154. return true;
  155. }
  156. int IMatchGroupDescription::GetServerPoolIndex( EMatchGroup eGroup, EMMServerMode eMode ) const
  157. {
  158. int nResult = (int)eGroup;
  159. switch ( eMode )
  160. {
  161. case eMMServerMode_Idle:
  162. {
  163. nResult = k_nGameServerPool_Idle;
  164. }
  165. break;
  166. case eMMServerMode_Full:
  167. {
  168. COMPILE_TIME_ASSERT( (int)k_nMatchGroup_MvM_Practice + (int)k_nGameServerPool_Full_First == (int)k_nGameServerPool_MvM_Practice_Full );
  169. nResult += k_nGameServerPool_MvM_Practice_Full;
  170. Assert( nResult >= k_nGameServerPool_Full_First );
  171. Assert( nResult <= k_nGameServerPool_Full_Last );
  172. return nResult;
  173. }
  174. break;
  175. case eMMServerMode_Incomplete_Match:
  176. {
  177. COMPILE_TIME_ASSERT( (int)k_nMatchGroup_MvM_Practice + (int)k_nGameServerPool_Incomplete_Match_First == (int)k_nGameServerPool_MvM_Practice_Incomplete_Match );
  178. nResult += k_nGameServerPool_Incomplete_Match_First;
  179. Assert( nResult >= k_nGameServerPool_Incomplete_Match_First );
  180. Assert( nResult <= k_nGameServerPool_Incomplete_Match_Last );
  181. }
  182. break;
  183. default:
  184. Assert( false );
  185. }
  186. return nResult;
  187. }
  188. #endif
  189. #ifdef GAME_DLL
  190. bool IMatchGroupDescription::InitServerSettingsForMatch( const CTFGSLobby* pLobby ) const
  191. {
  192. // Setting servercfgfile to our mode-specific config causes the server to exec it once it finishes
  193. // loading the map from the changelevel below
  194. servercfgfile.SetValue( m_params.m_pszExecFileName );
  195. lservercfgfile.SetValue( m_params.m_pszExecFileName );
  196. return TFGameRules()->StartManagedMatch();
  197. }
  198. #endif
  199. #ifdef CLIENT_DLL
  200. #ifdef STAGING_ONLY
  201. void cc_tf_test_pvp_rank_xp_change( IConVar *pConVar, const char *pOldString, float flOldValue )
  202. {
  203. IGameEvent *pEvent = gameeventmanager->CreateEvent( "experience_changed" );
  204. if ( pEvent )
  205. {
  206. gameeventmanager->FireEventClientSide( pEvent );
  207. }
  208. }
  209. ConVar tf_test_pvp_rank_xp_change( "tf_test_pvp_rank_xp_change", "-1", 0, "Force your experience to a specific value", cc_tf_test_pvp_rank_xp_change );
  210. CON_COMMAND_F( tf_progression_set_xp_to_level, "Overrides your XP to be within the range the level specified", FCVAR_CHEAT )
  211. {
  212. if ( args.ArgC() != 3 )
  213. {
  214. Msg( "Usage tf_progression_set_xp_to_level <matchgroup> <level>\n" );
  215. return;
  216. }
  217. const IMatchGroupDescription* pMatch = GetMatchGroupDescription( (EMatchGroup)atoi( args[1] ) );
  218. if ( !pMatch || !pMatch->m_pProgressionDesc )
  219. return;
  220. const LevelInfo_t& level = pMatch->m_pProgressionDesc->GetLevelByNumber( atoi( args[2] ) );
  221. tf_test_pvp_rank_xp_change.SetValue( RandomInt( level.m_nStartXP, level.m_nEndXP ) );
  222. IGameEvent *pEvent = gameeventmanager->CreateEvent( "experience_changed" );
  223. if ( pEvent )
  224. {
  225. gameeventmanager->FireEventClientSide( pEvent );
  226. }
  227. pEvent = gameeventmanager->CreateEvent( "begin_xp_lerp" );
  228. if ( pEvent )
  229. {
  230. gameeventmanager->FireEventClientSide( pEvent );
  231. }
  232. }
  233. CON_COMMAND_F( tf_progression_set_xp_to_value, "Overrides your XP to be a specific value", FCVAR_CHEAT )
  234. {
  235. if ( args.ArgC() != 3 )
  236. {
  237. Msg( "Usage tf_progression_set_xp_to_level <matchgroup> <value>\n" );
  238. return;
  239. }
  240. const IMatchGroupDescription* pMatch = GetMatchGroupDescription( (EMatchGroup)atoi( args[1] ) );
  241. if ( !pMatch || !pMatch->m_pProgressionDesc )
  242. return;
  243. tf_test_pvp_rank_xp_change.SetValue( atoi( args[2] ) );
  244. IGameEvent *pEvent = gameeventmanager->CreateEvent( "experience_changed" );
  245. if ( pEvent )
  246. {
  247. gameeventmanager->FireEventClientSide( pEvent );
  248. }
  249. pEvent = gameeventmanager->CreateEvent( "begin_xp_lerp" );
  250. if ( pEvent )
  251. {
  252. gameeventmanager->FireEventClientSide( pEvent );
  253. }
  254. }
  255. #endif // STAGING_ONLY
  256. #endif // CLIENT_DLL
  257. // Casual XP constants
  258. const float flAverageXPPerGame = 500.f;
  259. const float flAverageMinutesPerGame = 30;
  260. // The target XP per minute
  261. const float flTargetXPPM = (float)flAverageXPPerGame / (float)flAverageMinutesPerGame;
  262. // The target breakdown at the end of a match
  263. const float flScoreXPScale = 0.4485f;
  264. const float flObjectiveXPScale = 0.15f;
  265. const float flMatchCompletionXPScale = 0.3f;
  266. // These come from the first 4 weeks of MyM match data
  267. const float flAvgPPMPM = 27.f; // Points per minute per match
  268. const float flAvgPPMPP = 1.15f; // Points per minute per player (above / 24)
  269. const XPSourceDef_t g_XPSourceDefs[] = { { "MatchMaking.XPChime", "TF_XPSource_PositiveFormat", "#TF_XPSource_Score", flTargetXPPM * flScoreXPScale / flAvgPPMPP /* 6.5 */ } // SOURCE_SCORE = 0;
  270. , { "MatchMaking.XPChime", "TF_XPSource_PositiveFormat", "#TF_XPSource_ObjectiveBonus", flTargetXPPM * flObjectiveXPScale / flAvgPPMPM /* 0.0926 */ } // SOURCE_OBJECTIVE_BONUS = 1;
  271. , { "MatchMaking.XPChime", "TF_XPSource_PositiveFormat", "#TF_XPSource_CompletedMatch", flTargetXPPM * flMatchCompletionXPScale / flAvgPPMPM /* 0.185 */ } // SOURCE_COMPLETED_MATCH = 2;
  272. , { "MVM.PlayerDied", "TF_XPSource_NoValueFormat", "#TF_XPSource_Comp_Abandon", 1.f } // SOURCE_COMPETITIVE_ABANDON = 3;
  273. , { "MatchMaking.XPChime", "TF_XPSource_NoValueFormat", "#TF_XPSource_Comp_Win", 1.f } // SOURCE_COMPETITIVE_WIN = 4;
  274. , { NULL, "TF_XPSource_NoValueFormat", "#TF_XPSource_Comp_Loss", 1.f } // SOURCE_COMPETITIVE_LOSS = 5;
  275. , { "MatchMaking.XPChime", "TF_XPSource_PositiveFormat", "#TF_XPSource_Autobalance_Bonus", 1.f } }; // SOURCE_AUTOBALANCE_BONUS = 6;
  276. IProgressionDesc::IProgressionDesc( EMatchGroup eMatchGroup
  277. , const char* pszBadgeName
  278. , const char* pszProgressionResFile
  279. , const char* pszLevelToken )
  280. : m_eMatchGroup( eMatchGroup )
  281. , m_strBadgeName( pszBadgeName )
  282. , m_pszProgressionResFile( pszProgressionResFile )
  283. , m_pszLevelToken( pszLevelToken )
  284. {}
  285. #ifdef CLIENT_DLL
  286. void IProgressionDesc::EnsureBadgePanelModel( CBaseModelPanel *pModelPanel ) const
  287. {
  288. studiohdr_t* pHDR = pModelPanel->GetStudioHdr();
  289. if ( !pHDR || !(CUtlString( pHDR->name ).UnqualifiedFilename() == m_strBadgeName.UnqualifiedFilename()) )
  290. {
  291. pModelPanel->SetMDL( m_strBadgeName );
  292. }
  293. }
  294. const LevelInfo_t& IProgressionDesc::YieldingGetLevelForSteamID( const CSteamID& steamID ) const
  295. {
  296. return GetLevelForExperience( GetPlayerExperienceBySteamID( steamID ) );
  297. }
  298. #endif // CLIENT_DLL
  299. const LevelInfo_t& IProgressionDesc::GetLevelByNumber( uint32 nNumber ) const
  300. {
  301. int nIndex = nNumber;
  302. nIndex = Clamp( nIndex - 1, 0, m_vecLevels.Count() - 1 );
  303. Assert( nIndex >= 0 && nIndex < m_vecLevels.Count() );
  304. return m_vecLevels[ nIndex ];
  305. };
  306. const LevelInfo_t& IProgressionDesc::GetLevelForExperience( uint32 nExperience ) const
  307. {
  308. uint32 nNumLevels = (uint32)m_vecLevels.Count();
  309. // Walk the levels to find where the passed in experience value falls
  310. for( uint32 i=0; i<nNumLevels; ++i )
  311. {
  312. if ( nExperience >= m_vecLevels[ i ].m_nStartXP && ( nExperience < m_vecLevels[ i ].m_nEndXP || (i + 1) == nNumLevels ) )
  313. {
  314. return m_vecLevels[ i ];
  315. }
  316. }
  317. Assert( false );
  318. return m_vecLevels[ 0 ];
  319. }
  320. class CMvMMatchGroupDescription : public IMatchGroupDescription
  321. {
  322. public:
  323. CMvMMatchGroupDescription( EMatchGroup eMatchGroup, const char* pszConfig, bool bTrustedOnly )
  324. : IMatchGroupDescription( eMatchGroup
  325. , { eMatchMode_MatchMaker_LateJoinDropIn // m_eLateJoinMode;
  326. , eMMPenaltyPool_Casual // m_ePenaltyPool
  327. , false // m_bUsesSkillRatings;
  328. , false // m_bSupportsLowPriorityQueue;
  329. , false // m_bRequiresMatchID;
  330. , MVM_REQUIRED_SCORE // m_pmm_required_score;
  331. , false // m_bUseMatchHud;
  332. , pszConfig // m_pszExecFileName;
  333. , &tf_mm_match_size_mvm // m_pmm_match_group_size;
  334. , NULL // m_pmm_match_group_size_minimum;
  335. , MATCH_TYPE_MVM // m_eMatchType;
  336. , false // m_bShowPreRoundDoors;
  337. , false // m_bShowPostRoundDoors;
  338. , NULL // m_pszMatchEndKickWarning;
  339. , NULL // m_pszMatchStartSound;
  340. , false // m_bAutoReady;
  341. , false // m_bShowRankIcons;
  342. , false // m_bUseMatchSummaryStage;
  343. , false // m_bDistributePerformanceMedals;
  344. , false // m_bIsCompetitiveMode;
  345. , false // m_bUseFirstBlood;
  346. , false // m_bUseReducedBonusTime;
  347. , false // m_bUseAutoBalance;
  348. , false // m_bAllowTeamChange;
  349. , true // m_bRandomWeaponCrits;
  350. , false // m_bFixedWeaponSpread;
  351. , false // m_bRequireCompleteMatch;
  352. , bTrustedOnly // m_bTrustedServersOnly;
  353. , false // m_bForceClientSettings;
  354. , false // m_bAllowDrawingAtMatchSummary
  355. , true // m_bAllowSpecModeChange
  356. , false // m_bAutomaticallyRequeueAfterMatchEnds
  357. , false // m_bUsesMapVoteOnRoundEnd
  358. , false // m_bUsesXP
  359. , false // m_bUsesDashboardOnRoundEnd
  360. , false // m_bUsesSurveys
  361. , false } ) // m_bStrictMatchmakerScoring
  362. {}
  363. #ifdef GC_DLL
  364. virtual EMMRating PrimaryMMRatingBackend() const OVERRIDE { return k_nMMRating_Invalid; }
  365. virtual const std::vector< EMMRating > &MatchResultRatingBackends() const OVERRIDE
  366. {
  367. static std::vector< EMMRating > mvmRatings = { /* crickets */ };
  368. return mvmRatings;
  369. }
  370. // Copy the party's search challenges
  371. bool InitMatchFromParty( MatchDescription_t* pMatch, const MatchParty_t* pParty ) const OVERRIDE
  372. {
  373. pMatch->m_setAcceptableChallenges = pParty->m_setSearchChallenges;
  374. #ifdef USE_MVM_TOUR
  375. if ( pMatch->m_eMatchGroup == k_nMatchGroup_MvM_MannUp )
  376. {
  377. Assert( pParty->m_iMannUpTourOfDuty >= 0 );
  378. pMatch->m_iMannUpTourOfDuty = pParty->m_iMannUpTourOfDuty;
  379. }
  380. else
  381. {
  382. Assert( pParty->m_iMannUpTourOfDuty == k_iMvmTourIndex_NotMannedUp );
  383. pMatch->m_iMannUpTourOfDuty = k_iMvmTourIndex_NotMannedUp;
  384. }
  385. #endif // USE_MVM_TOUR
  386. return true;
  387. }
  388. virtual bool InitMatchFromLobby( MatchDescription_t* pMatch, CTFLobby* pLobby ) const OVERRIDE
  389. {
  390. pMatch->m_setAcceptableChallenges.Clear();
  391. pMatch->m_setAcceptableChallenges.SetMissionBySchemaIndex( pLobby->GetMissionIndex(), true );
  392. #ifdef USE_MVM_TOUR
  393. if ( pLobby->GetMatchGroup() == k_nMatchGroup_MvM_MannUp )
  394. {
  395. pMatch->m_iMannUpTourOfDuty = pLobby->GetMannUpTourIndex();
  396. }
  397. else
  398. {
  399. Assert( pLobby->GetMannUpTourIndex() == k_iMvmTourIndex_NotMannedUp );
  400. }
  401. #endif // USE_MVM_TOUR
  402. return true;
  403. }
  404. // Sync selected MvM challenges
  405. virtual void SyncMatchParty( const CTFParty *pParty, MatchParty_t *pMatchParty ) const OVERRIDE
  406. {
  407. pParty->GetSearchChallenges( pMatchParty->m_setSearchChallenges );
  408. }
  409. // Go through our selected challenges and pick a random challenge then a random popfile from the chosen challenge
  410. virtual void SelectModeSpecificParameters( const MatchDescription_t* pMatch, CTFLobby* pLobby ) const OVERRIDE
  411. {
  412. if ( !pMatch )
  413. return;
  414. int nCountPops = GetItemSchema()->GetMvmMissions().Count();
  415. int nSelectedChallenge = -1;
  416. nSelectedChallenge = -1;
  417. int n = 0;
  418. for ( int i = 0 ; i < nCountPops ; ++i )
  419. {
  420. if ( pMatch->m_setAcceptableChallenges.GetMissionBySchemaIndex( i ) )
  421. {
  422. int r = RandomInt( 0, n );
  423. if ( r == 0 )
  424. {
  425. nSelectedChallenge = i;
  426. }
  427. ++n;
  428. }
  429. }
  430. // We *should* have chosen one by now, unless the schema is hosed.
  431. // But if we haven't, force it to be selected now
  432. if ( nSelectedChallenge < 0 )
  433. {
  434. Assert( nSelectedChallenge >= 0 );
  435. nSelectedChallenge = RandomInt( 0, nCountPops-1 );
  436. }
  437. Assert( nSelectedChallenge >= 0 );
  438. pLobby->SetMapName( GetItemSchema()->GetMvmMissions()[ nSelectedChallenge ].m_sMapNameActual.Get() );
  439. pLobby->SetMissionName( GetItemSchema()->GetMvmMissions()[ nSelectedChallenge ].m_sPop.Get() );
  440. #ifdef USE_MVM_TOUR
  441. if ( pMatch->m_eMatchGroup == k_nMatchGroup_MvM_MannUp )
  442. {
  443. Assert( pMatch->m_iMannUpTourOfDuty >= 0 );
  444. pLobby->SetMannUpTourName( GetItemSchema()->GetMvmTours()[pMatch->m_iMannUpTourOfDuty].m_sTourInternalName.Get() );
  445. }
  446. else
  447. {
  448. Assert( pMatch->m_iMannUpTourOfDuty == k_iMvmTourIndex_NotMannedUp );
  449. }
  450. #endif // USE_MVM_TOUR
  451. }
  452. // Check MvM challenges have an intersection between the current and searching parties
  453. virtual bool BThreadedPartyCompatibleWithMatch( const MatchDescription_t* pMatch, const MatchParty_t *pCandidateParty ) const OVERRIDE
  454. {
  455. // Check for blacklisted former members that tank compatibility
  456. if ( !BCheckLeftMatchMembersAgainstParty( pMatch, pCandidateParty, &BThreadedFormerMemberMayRejoinJoinCasualOrMvMMatch) )
  457. { return false; }
  458. #ifdef USE_MVM_TOUR
  459. return pCandidateParty->m_iMannUpTourOfDuty == pMatch->m_iMannUpTourOfDuty;
  460. #endif // USE_MVM_TOUR
  461. return pMatch->m_setAcceptableChallenges.HasIntersection( pCandidateParty->m_setSearchChallenges );
  462. }
  463. virtual bool BThreadedPartiesCompatible( const MatchParty_t *pLeftParty, const MatchParty_t *pRightParty ) const OVERRIDE
  464. {
  465. #ifdef USE_MVM_TOUR
  466. if ( pLeftParty->m_iMannUpTourOfDuty != pRightParty->m_iMannUpTourOfDuty )
  467. return false;
  468. #endif // USE_MVM_TOUR
  469. return !pLeftParty->m_setSearchChallenges.HasIntersection( pRightParty->m_setSearchChallenges );
  470. }
  471. // Intersect MvM challenges
  472. bool BThreadedIntersectMatchWithParty( MatchDescription_t* pMatch, const MatchParty_t* pParty ) const OVERRIDE
  473. {
  474. // Check for blacklisted former members that tank compatibility
  475. if ( !BCheckLeftMatchMembersAgainstParty( pMatch, pParty, &BThreadedFormerMemberMayRejoinJoinCasualOrMvMMatch) )
  476. { return false; }
  477. pMatch->m_setAcceptableChallenges.Intersect( pParty->m_setSearchChallenges );
  478. if ( pMatch->m_setAcceptableChallenges.IsEmpty() )
  479. return false;
  480. #ifdef USE_MVM_TOUR
  481. if ( pMatch->m_eMatchGroup == k_nMatchGroup_MvM_MannUp )
  482. {
  483. Assert( pParty->m_iMannUpTourOfDuty >= 0 );
  484. if ( pMatch->m_iMannUpTourOfDuty != pParty->m_iMannUpTourOfDuty )
  485. return false;
  486. }
  487. else
  488. {
  489. Assert( pParty->m_iMannUpTourOfDuty == k_iMvmTourIndex_NotMannedUp );
  490. }
  491. #endif // USE_MVM_TOUR
  492. return true;
  493. }
  494. virtual void GetServerDetails( const CMsgGameServerMatchmakingStatus& msg, int& nChallengeIndex, const char* pszMap ) const OVERRIDE
  495. {
  496. if ( msg.matchmaking_state() == ServerMatchmakingState_EMPTY ) // if we're empty, we can switch the challenge, so the current value doesn't matter
  497. {
  498. pszMap = "";
  499. }
  500. else
  501. {
  502. nChallengeIndex = GetItemSchema()->FindMvmMissionByName( pszMap );
  503. }
  504. }
  505. virtual const char* GetUnauthorizedPartyReason( CTFParty* pParty ) const OVERRIDE
  506. {
  507. pParty->CheckRemoveInvalidSearchChallenges();
  508. CMvMMissionSet searchChallenges;
  509. pParty->GetSearchChallenges( searchChallenges );
  510. if ( searchChallenges.IsEmpty() )
  511. {
  512. return "They want to play MvM, but set of search challenges is empty.";
  513. }
  514. if ( pParty->GetMatchGroup() == k_nMatchGroup_MvM_MannUp )
  515. {
  516. TFPartyManager()->YldUpdatePartyMemberData( pParty );
  517. if ( pParty->BAnyMemberWithoutTicket() )
  518. {
  519. return "They want to play MannUp, but somebody doesn't have a ticket.";
  520. }
  521. #ifdef USE_MVM_TOUR
  522. // Make sure we know what tour of duty the want to play
  523. if ( pParty->GetSearchMannUpTourIndex() < 0 )
  524. {
  525. return "They want to play MannUp, but no tour of duty specified.";
  526. }
  527. #endif // USE_MVM_TOUR
  528. }
  529. return NULL;
  530. }
  531. virtual void Dump( const char *pszLeader, int nSpewLevel, int nLogLevel, const MatchParty_t* pMatch ) const OVERRIDE
  532. {
  533. CUtlString sSelectedPops;
  534. int n = 0;
  535. for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i )
  536. {
  537. if ( pMatch->m_setSearchChallenges.GetMissionBySchemaIndex( i ) )
  538. {
  539. if ( n > 0 )
  540. sSelectedPops += ", ";
  541. if ( n >= 5 )
  542. {
  543. sSelectedPops += "...";
  544. break;
  545. }
  546. sSelectedPops += GetItemSchema()->GetMvmMissionName( i );
  547. ++n;
  548. }
  549. }
  550. EmitInfo( SPEW_GC, nSpewLevel, nLogLevel, "%s MvM Type: %s Search Pop: %s\n", pszLeader,
  551. ( m_eMatchGroup == k_nMatchGroup_MvM_MannUp ? "Mann Up" : ( m_eMatchGroup == k_nMatchGroup_MvM_Practice ? "Bootcamp" : "UNKNOWN" ) ),
  552. sSelectedPops.String() );
  553. EmitInfo( SPEW_GC, nSpewLevel, nLogLevel, "%s Best valve data center ping: %.0fms\n", pszLeader, pMatch->m_flPingClosestServer );
  554. }
  555. #endif
  556. #ifdef CLIENT_DLL
  557. virtual bool BGetRoundStartBannerParameters( int& nSkin, int& nBodyGroup ) const OVERRIDE
  558. {
  559. // Dont show in MvM...for now
  560. return false;
  561. }
  562. virtual bool BGetRoundDoorParameters( int& nSkin, int& nLogoBodyGroup ) const OVERRIDE
  563. {
  564. // Don't show in MvM...for now
  565. return false;
  566. }
  567. virtual const char *GetMapLoadBackgroundOverride( bool bWidescreen ) const OVERRIDE
  568. {
  569. if ( bWidescreen )
  570. {
  571. return NULL;
  572. }
  573. return "mvm_background_map";
  574. }
  575. #endif
  576. #ifdef GAME_DLL
  577. virtual bool InitServerSettingsForMatch( const CTFGSLobby* pLobby ) const OVERRIDE
  578. {
  579. bool bRet = IMatchGroupDescription::InitServerSettingsForMatch( pLobby );
  580. if ( *pLobby->GetMissionName() != '\0' )
  581. {
  582. TFGameRules()->SetNextMvMPopfile( pLobby->GetMissionName() );
  583. }
  584. return bRet;
  585. }
  586. virtual void PostMatchClearServerSettings() const OVERRIDE
  587. {
  588. }
  589. virtual void InitGameRulesSettings() const OVERRIDE
  590. {
  591. }
  592. virtual void InitGameRulesSettingsPostEntity() const OVERRIDE
  593. {
  594. }
  595. bool ShouldRequestLateJoin() const OVERRIDE
  596. {
  597. if ( !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() )
  598. return false;
  599. // Check game state
  600. switch ( TFGameRules()->State_Get() )
  601. {
  602. case GR_STATE_INIT:
  603. case GR_STATE_PREGAME:
  604. case GR_STATE_STARTGAME:
  605. case GR_STATE_PREROUND:
  606. case GR_STATE_TEAM_WIN:
  607. case GR_STATE_RESTART:
  608. case GR_STATE_STALEMATE:
  609. case GR_STATE_BONUS:
  610. case GR_STATE_BETWEEN_RNDS:
  611. return true;
  612. case GR_STATE_RND_RUNNING:
  613. if ( TFObjectiveResource() &&
  614. !TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() &&
  615. TFObjectiveResource()->GetMannVsMachineWaveCount() == TFObjectiveResource()->GetMannVsMachineMaxWaveCount() )
  616. {
  617. int nMaxEnemyCountNoSupport = TFObjectiveResource()->GetMannVsMachineWaveEnemyCount();
  618. if ( nMaxEnemyCountNoSupport <= 0 )
  619. {
  620. Assert( false ); // no enemies in wave?!
  621. return false;
  622. }
  623. // calculate number of remaining enemies
  624. int nNumEnemyRemaining = 0;
  625. for ( int i = 0; i < MVM_CLASS_TYPES_PER_WAVE_MAX_NEW; ++i )
  626. {
  627. int nClassCount = TFObjectiveResource()->GetMannVsMachineWaveClassCount( i );
  628. unsigned int iFlags = TFObjectiveResource()->GetMannVsMachineWaveClassFlags( i );
  629. if ( iFlags & MVM_CLASS_FLAG_MINIBOSS )
  630. {
  631. nNumEnemyRemaining += nClassCount;
  632. }
  633. if ( iFlags & MVM_CLASS_FLAG_NORMAL )
  634. {
  635. nNumEnemyRemaining += nClassCount;
  636. }
  637. }
  638. // if less then 40% of the last wave remains, lock people out from MM
  639. if ( (float)nNumEnemyRemaining / (float)nMaxEnemyCountNoSupport < 0.4f )
  640. return false;
  641. }
  642. return true;
  643. case GR_STATE_GAME_OVER:
  644. return false;
  645. }
  646. Assert( false );
  647. return false;
  648. }
  649. bool BMatchIsSafeToLeaveForPlayer( const CMatchInfo* pMatchInfo, const CMatchInfo::PlayerMatchData_t *pMatchPlayer ) const
  650. {
  651. bool bSafe = false;
  652. // Allow safe leaving after you have played for N seconds or if the match drops below N players, even if it is
  653. // still active.
  654. int nAllowAfterSeconds = tf_mvm_allow_abandon_after_seconds.GetInt();
  655. int nAllowBelowPlayers = tf_mvm_allow_abandon_below_players.GetInt();
  656. RTime32 now = CRTime::RTime32TimeCur();
  657. bSafe = bSafe || ( nAllowAfterSeconds > 0 && (uint32)nAllowAfterSeconds < ( now - pMatchPlayer->rtJoinedMatch ) );
  658. bSafe = bSafe || ( nAllowBelowPlayers > 0 && pMatchInfo->GetNumActiveMatchPlayers() < nAllowBelowPlayers );
  659. // Bootcamp is a magical nevar-abandon land
  660. bSafe = bSafe || ( m_eMatchGroup == k_nMatchGroup_MvM_Practice );
  661. return bSafe;
  662. }
  663. virtual bool BPlayWinMusic( int nWinningTeam, bool bGameOver ) const OVERRIDE
  664. {
  665. // Not handled
  666. return false;
  667. }
  668. #endif
  669. };
  670. class CLadderMatchGroupDescription : public IMatchGroupDescription
  671. {
  672. public:
  673. class CLadderProgressionDesc : public IProgressionDesc
  674. {
  675. public:
  676. CLadderProgressionDesc( EMatchGroup eMatchGroup )
  677. : IProgressionDesc( eMatchGroup
  678. , "models/vgui/competitive_badge.mdl"
  679. , "resource/ui/PvPCompRankPanel.res"
  680. , "TF_Competitive_Rank" )
  681. {
  682. // Bucket 1
  683. m_vecLevels.AddToTail( { 1, k_unDrilloRating_Ladder_Min, 11500, "competitive/competitive_badge_rank001", "#TF_Competitive_Rank_1", "MatchMaking.RankOneAchieved", "competitive/comp_background_tier001a" } );
  684. m_vecLevels.AddToTail( { 2, m_vecLevels.Tail().m_nEndXP, 13000, "competitive/competitive_badge_rank002", "#TF_Competitive_Rank_2", "MatchMaking.RankOneAchieved", "competitive/comp_background_tier001a" } );
  685. m_vecLevels.AddToTail( { 3, m_vecLevels.Tail().m_nEndXP, 14500, "competitive/competitive_badge_rank003", "#TF_Competitive_Rank_3", "MatchMaking.RankOneAchieved", "competitive/comp_background_tier001a" } );
  686. m_vecLevels.AddToTail( { 4, m_vecLevels.Tail().m_nEndXP, 16000, "competitive/competitive_badge_rank004", "#TF_Competitive_Rank_4", "MatchMaking.RankOneAchieved", "competitive/comp_background_tier002a" } );
  687. m_vecLevels.AddToTail( { 5, m_vecLevels.Tail().m_nEndXP, 17500, "competitive/competitive_badge_rank005", "#TF_Competitive_Rank_5", "MatchMaking.RankOneAchieved", "competitive/comp_background_tier002a" } );
  688. m_vecLevels.AddToTail( { 6, m_vecLevels.Tail().m_nEndXP, 19500, "competitive/competitive_badge_rank006", "#TF_Competitive_Rank_6", "MatchMaking.RankOneAchieved", "competitive/comp_background_tier002a" } );
  689. // Bucket 2
  690. m_vecLevels.AddToTail( { 7, m_vecLevels.Tail().m_nEndXP, 21500, "competitive/competitive_badge_rank007", "#TF_Competitive_Rank_7", "MatchMaking.RankTwoAchieved", "competitive/comp_background_tier003a" } );
  691. m_vecLevels.AddToTail( { 8, m_vecLevels.Tail().m_nEndXP, 23500, "competitive/competitive_badge_rank008", "#TF_Competitive_Rank_8", "MatchMaking.RankTwoAchieved", "competitive/comp_background_tier003a" } );
  692. m_vecLevels.AddToTail( { 9, m_vecLevels.Tail().m_nEndXP, 25500, "competitive/competitive_badge_rank009", "#TF_Competitive_Rank_9", "MatchMaking.RankTwoAchieved", "competitive/comp_background_tier003a" } );
  693. m_vecLevels.AddToTail( { 10, m_vecLevels.Tail().m_nEndXP, 28000, "competitive/competitive_badge_rank010", "#TF_Competitive_Rank_10", "MatchMaking.RankTwoAchieved", "competitive/comp_background_tier004a" } );
  694. m_vecLevels.AddToTail( { 11, m_vecLevels.Tail().m_nEndXP, 30500, "competitive/competitive_badge_rank011", "#TF_Competitive_Rank_11", "MatchMaking.RankTwoAchieved", "competitive/comp_background_tier004a" } );
  695. m_vecLevels.AddToTail( { 12, m_vecLevels.Tail().m_nEndXP, 33000, "competitive/competitive_badge_rank012", "#TF_Competitive_Rank_12", "MatchMaking.RankTwoAchieved", "competitive/comp_background_tier004a" } );
  696. // Bucket 3
  697. m_vecLevels.AddToTail( { 13, m_vecLevels.Tail().m_nEndXP, 35500, "competitive/competitive_badge_rank013", "#TF_Competitive_Rank_13", "MatchMaking.RankThreeAchieved", "competitive/comp_background_tier005a" } );
  698. m_vecLevels.AddToTail( { 14, m_vecLevels.Tail().m_nEndXP, 38000, "competitive/competitive_badge_rank014", "#TF_Competitive_Rank_14", "MatchMaking.RankThreeAchieved", "competitive/comp_background_tier005a" } );
  699. m_vecLevels.AddToTail( { 15, m_vecLevels.Tail().m_nEndXP, 40500, "competitive/competitive_badge_rank015", "#TF_Competitive_Rank_15", "MatchMaking.RankThreeAchieved", "competitive/comp_background_tier005a" } );
  700. m_vecLevels.AddToTail( { 16, m_vecLevels.Tail().m_nEndXP, 43500, "competitive/competitive_badge_rank016", "#TF_Competitive_Rank_16", "MatchMaking.RankThreeAchieved", "competitive/comp_background_tier006a" } );
  701. m_vecLevels.AddToTail( { 17, m_vecLevels.Tail().m_nEndXP, 46500, "competitive/competitive_badge_rank017", "#TF_Competitive_Rank_17", "MatchMaking.RankThreeAchieved", "competitive/comp_background_tier006a" } );
  702. // Bucket 4
  703. m_vecLevels.AddToTail( { 18, m_vecLevels.Tail().m_nEndXP, 50000, "competitive/competitive_badge_rank018", "#TF_Competitive_Rank_18", "MatchMaking.RankFourAchieved", "competitive/comp_background_tier006a" } );
  704. }
  705. const LevelInfo_t& GetLevelForExperience( uint32 nExperience ) const OVERRIDE
  706. {
  707. FixmeMMRatingBackendSwapping(); // Hard-coded drillo
  708. // The client may not have a rating yet, in which case they see 0 until they've been in a match. For level
  709. // purposes, return minimum.
  710. return IProgressionDesc::GetLevelForExperience( nExperience == 0 ? k_unDrilloRating_Ladder_Min : nExperience );
  711. }
  712. #ifdef CLIENT_DLL
  713. virtual void SetupBadgePanel( CBaseModelPanel *pModelPanel, const LevelInfo_t& level ) const OVERRIDE
  714. {
  715. if ( !pModelPanel )
  716. return;
  717. int nLevelIndex = level.m_nLevelNum - 1;
  718. int nSkin = nLevelIndex;
  719. int nSkullsBodygroup = ( nLevelIndex % 6 );
  720. int nSparkleBodygroup = 0;
  721. if ( level.m_nLevelNum == 18 ) nSparkleBodygroup = 1;
  722. EnsureBadgePanelModel( pModelPanel );
  723. int nBody = 0;
  724. CStudioHdr studioHDR( pModelPanel->GetStudioHdr(), g_pMDLCache );
  725. ::SetBodygroup( &studioHDR, nBody, ::FindBodygroupByName( &studioHDR, "skulls" ), nSkullsBodygroup );
  726. ::SetBodygroup( &studioHDR, nBody, ::FindBodygroupByName( &studioHDR, "sparkle" ), nSparkleBodygroup );
  727. pModelPanel->SetBody( nBody );
  728. pModelPanel->SetSkin( nSkin );
  729. }
  730. virtual const uint32 GetLocalPlayerLastAckdExperience() const OVERRIDE
  731. {
  732. // This is bad and hard-coding a match group. We should just make XP a rating type and these functions
  733. // should just say "use this rating for XP"/"use this rating for acked XP"
  734. FixmeMMRatingBackendSwapping();
  735. #if defined STAGING_ONLY
  736. if ( tf_test_pvp_rank_xp_change.GetInt() != -1 )
  737. {
  738. return tf_test_pvp_rank_xp_change.GetInt();
  739. }
  740. #endif
  741. if ( !steamapicontext || !steamapicontext->SteamUser() )
  742. {
  743. return 0u;
  744. }
  745. #ifndef CLIENT_DLL
  746. #error Make this call a yielding call if you are removing it from client ifdefs
  747. #endif
  748. CTFRatingData *pRating = CTFRatingData::YieldingGetPlayerRatingDataBySteamID( steamapicontext->SteamUser()->GetSteamID(),
  749. k_nMMRating_6v6_DRILLO_PlayerAcknowledged );
  750. return pRating ? pRating->GetRatingData().unRatingPrimary : 0u;
  751. }
  752. virtual const uint32 GetPlayerExperienceBySteamID( CSteamID steamid ) const OVERRIDE
  753. {
  754. // This is bad and hard-coding a match group. We should just make XP a rating type and these functions
  755. // should just say "use this rating for XP"/"use this rating for acked XP"
  756. FixmeMMRatingBackendSwapping();
  757. #if defined CLIENT_DLL && defined STAGING_ONLY
  758. if ( tf_test_pvp_rank_xp_change.GetInt() != -1 )
  759. {
  760. return tf_test_pvp_rank_xp_change.GetInt();
  761. }
  762. #endif
  763. #ifndef CLIENT_DLL
  764. #error Make this call a yielding call if you are removing it from client ifdefs
  765. #endif
  766. CTFRatingData *pRating = CTFRatingData::YieldingGetPlayerRatingDataBySteamID( steamapicontext->SteamUser()->GetSteamID(),
  767. k_nMMRating_6v6_DRILLO );
  768. return pRating ? pRating->GetRatingData().unRatingPrimary : 0u;
  769. }
  770. #endif // CLIENT_DLL
  771. #if defined GC
  772. virtual bool BYldAcknowledgePlayerXPOnTransaction( CSQLAccess &transaction,
  773. CTFSharedObjectCache *pLockedSOCache ) const OVERRIDE
  774. {
  775. // This is bad and a result of XP being just a rating in some places but a magic field elsewhere.
  776. FixmeMMRatingBackendSwapping();
  777. MMRatingData_t ratingData = pLockedSOCache->GetPlayerRatingData( k_nMMRating_6v6_DRILLO );
  778. MMRatingData_t ackedRatingData = pLockedSOCache->GetPlayerRatingData( k_nMMRating_6v6_DRILLO_PlayerAcknowledged );
  779. if ( ratingData == ackedRatingData )
  780. { return true; }
  781. // Feed it to acknowledged rating
  782. return pLockedSOCache->BYieldingUpdatePlayerRating( transaction,
  783. k_nMMRating_6v6_DRILLO_PlayerAcknowledged,
  784. k_nMMRatingSource_PlayerAcknowledge,
  785. 0,
  786. ratingData );
  787. }
  788. virtual const bool BRankXPIsActuallyPrimaryMMRating() const OVERRIDE
  789. {
  790. // Gross hack due to XP being magically used differently in 6v6 right now.
  791. FixmeMMRatingBackendSwapping();
  792. return true;
  793. }
  794. #endif // defined GC
  795. #if defined GC_DLL || ( defined STAGING_ONLY && defined CLIENT_DLL )
  796. virtual void DebugSpewLevels() const OVERRIDE
  797. {
  798. Msg( "Spewing comp levels:\n" );
  799. // Walk the levels to find where the passed in experience value falls
  800. for( int i=0; i< m_vecLevels.Count(); ++i )
  801. {
  802. Msg( "Level %d:\t%d - %d. (+%d)\n", m_vecLevels[i].m_nLevelNum, m_vecLevels[i].m_nStartXP, m_vecLevels[i].m_nEndXP, ( m_vecLevels[i].m_nEndXP - m_vecLevels[i].m_nStartXP ) );
  803. }
  804. }
  805. #endif
  806. };
  807. CLadderMatchGroupDescription( EMatchGroup eMatchGroup, ConVar* pmm_match_group_size )
  808. : IMatchGroupDescription( eMatchGroup
  809. , { eMatchMode_MatchMaker_LateJoinMatchBased // m_eLateJoinMode;
  810. , eMMPenaltyPool_Ranked // m_ePenaltyPool
  811. , true // m_bUsesSkillRatings;
  812. , true // m_bSupportsLowPriorityQueue;
  813. , true // m_bRequiresMatchID;
  814. , LADDER_REQUIRED_SCORE // m_pmm_required_score;
  815. , true // m_bUseMatchHud;
  816. , "server_competitive.cfg" // m_pszExecFileName;
  817. , pmm_match_group_size // m_pmm_match_group_size;
  818. , NULL // m_pmm_match_group_size_minimum;
  819. , MATCH_TYPE_COMPETITIVE // m_eMatchType;
  820. , true // m_bShowPreRoundDoors;
  821. , true // m_bShowPostRoundDoors;
  822. , "#TF_Competitive_GameOver" // m_pszMatchEndKickWarning;
  823. , "MatchMaking.RoundStart" // m_pszMatchStartSound;
  824. , false // m_bAutoReady;
  825. , true // m_bShowRankIcons;
  826. , true // m_bUseMatchSummaryStage;
  827. , true // m_bDistributePerformanceMedals;
  828. , true // m_bIsCompetitiveMode;
  829. , true // m_bUseFirstBlood;
  830. , true // m_bUseReducedBonusTime;
  831. , false // m_bUseAutoBalance;
  832. , false // m_bAllowTeamChange;
  833. , false // m_bRandomWeaponCrits;
  834. , true // m_bFixedWeaponSpread;
  835. , true // m_bRequireCompleteMatch;
  836. , true // m_bTrustedServersOnly;
  837. , true // m_bForceClientSettings;
  838. , true // m_bAllowDrawingAtMatchSummary
  839. , false // m_bAllowSpecModeChange
  840. , false // m_bAutomaticallyRequeueAfterMatchEnds
  841. , false // m_bUsesMapVoteOnRoundEnd
  842. , false // m_bUsesXP
  843. , true // m_bUsesDashboardOnRoundEnd
  844. , true // m_bUsesSurveys
  845. , true } ) // m_bStrictMatchmakerScoring
  846. {
  847. m_pProgressionDesc = new CLadderProgressionDesc( eMatchGroup );
  848. }
  849. #ifdef GC_DLL
  850. virtual EMMRating PrimaryMMRatingBackend() const OVERRIDE
  851. {
  852. // Right now all ladders have this hard-coded. Live-swapping won't work either, since lobbies don't know what
  853. // they were formed with in Match_Result
  854. FixmeMMRatingBackendSwapping();
  855. return k_nMMRating_6v6_DRILLO;
  856. }
  857. virtual const std::vector< EMMRating > &MatchResultRatingBackends() const OVERRIDE
  858. {
  859. FixmeMMRatingBackendSwapping(); // Shouldn't be hard-coded for 6v6, param
  860. static std::vector< EMMRating > ladderRatings = { k_nMMRating_6v6_DRILLO, k_nMMRating_6v6_GLICKO };
  861. return ladderRatings;
  862. }
  863. virtual bool InitMatchFromParty( MatchDescription_t* pMatch, const MatchParty_t* pParty ) const OVERRIDE { return true; }
  864. virtual bool InitMatchFromLobby( MatchDescription_t* pMatch, CTFLobby* pLobby ) const OVERRIDE { return true; }
  865. virtual void SyncMatchParty( const CTFParty *pParty, MatchParty_t *pMatchParty ) const OVERRIDE {};
  866. virtual void SelectModeSpecificParameters( const MatchDescription_t* pMatch, CTFLobby* pLobby ) const OVERRIDE
  867. {
  868. // Forced to use the competitive category
  869. const SchemaGameCategory_t* pCategory = GetItemSchema()->GetGameCategory( kGameCategory_Competitive_6v6 );
  870. const char *pszMap = ( *tf_mm_ladder_force_map_by_name.GetString() ) ? tf_mm_ladder_force_map_by_name.GetString() : pCategory->GetRandomMap()->pszMapName;
  871. pLobby->SetMapName( pszMap );
  872. }
  873. virtual void GetServerDetails( const CMsgGameServerMatchmakingStatus& msg, int& nChallengeIndex, const char* pszMap ) const OVERRIDE
  874. {}
  875. virtual bool BThreadedPartiesCompatible( const MatchParty_t *pLeftParty, const MatchParty_t *pRightParty ) const OVERRIDE
  876. {
  877. return true;
  878. }
  879. virtual bool BThreadedPartyCompatibleWithMatch( const MatchDescription_t* pMatch, const MatchParty_t *pCurrentParty ) const OVERRIDE
  880. {
  881. // Right now there's no criteria on ladder matches, but leavers are never allowed to rejoin
  882. return BCheckLeftMatchMembersAgainstParty( pMatch, pCurrentParty,
  883. [](const CTFMemcachedLobbyFormerMember& member) { return false; } );
  884. }
  885. virtual bool BThreadedIntersectMatchWithParty( MatchDescription_t* pMatch, const MatchParty_t* pParty ) const OVERRIDE
  886. {
  887. // No action on match, just check that they're compatible
  888. return BThreadedPartyCompatibleWithMatch( pMatch, pParty );
  889. }
  890. virtual const char* GetUnauthorizedPartyReason( CTFParty* pParty ) const OVERRIDE
  891. {
  892. TFPartyManager()->YldUpdatePartyMemberData( pParty );
  893. if ( pParty->BAnyMemberWithoutCompetitiveAccess() )
  894. {
  895. return "They want to play a competitive game, but somebody doesn't have competitive access";
  896. }
  897. return NULL;
  898. }
  899. virtual void Dump( const char *pszLeader, int nSpewLevel, int nLogLevel, const MatchParty_t* pMatch ) const OVERRIDE
  900. {
  901. EmitInfo( SPEW_GC, nSpewLevel, nLogLevel, "%s Best Ladder ping: %.0fms\n", pszLeader, pMatch->m_flPingClosestServer );
  902. }
  903. #endif
  904. #ifdef CLIENT_DLL
  905. virtual bool BGetRoundStartBannerParameters( int& nSkin, int& nBodyGroup ) const OVERRIDE
  906. {
  907. // The comp skins start at skin 8
  908. nSkin = 8 + TFGameRules()->GetRoundsPlayed();
  909. nBodyGroup = 1;
  910. return true;
  911. }
  912. virtual bool BGetRoundDoorParameters( int& nSkin, int& nLogoBodyGroup ) const OVERRIDE
  913. {
  914. nLogoBodyGroup = 0;
  915. nSkin = 0;
  916. if( GTFGCClientSystem()->GetLobby() &&
  917. GTFGCClientSystem()->GetLobby()->Obj().average_rank() >= k_unDrilloRating_Ladder_HighSkill )
  918. {
  919. // High skill has a different skin
  920. nSkin = 1;
  921. }
  922. return true;
  923. }
  924. virtual const char *GetMapLoadBackgroundOverride( bool bWidescreen ) const OVERRIDE
  925. {
  926. return ( bWidescreen ? "ranked_background_widescreen" : "ranked_background" );
  927. }
  928. #endif
  929. #ifdef GAME_DLL
  930. virtual void PostMatchClearServerSettings() const OVERRIDE
  931. {
  932. Assert( TFGameRules() );
  933. TFGameRules()->EndCompetitiveMatch();
  934. }
  935. virtual void InitGameRulesSettings() const OVERRIDE
  936. {
  937. TFGameRules()->SetCompetitiveMode( true );
  938. TFGameRules()->SetAllowBetweenRounds( true );
  939. }
  940. virtual void InitGameRulesSettingsPostEntity() const OVERRIDE
  941. {
  942. CTeamControlPointMaster *pMaster = ( g_hControlPointMasters.Count() ) ? g_hControlPointMasters[0] : NULL;
  943. bool bMultiStagePLR = ( tf_gamemode_payload.GetBool() && pMaster && pMaster->PlayingMiniRounds() && TFGameRules()->HasMultipleTrains() );
  944. bool bUseStopWatch = TFGameRules()->MatchmakingShouldUseStopwatchMode();
  945. bool bCTF = tf_gamemode_ctf.GetBool();
  946. bool bHighSkill = GTFGCClientSystem()->GetMatch() && GTFGCClientSystem()->GetMatch()->m_uAverageRank >= k_unDrilloRating_Ladder_HighSkill;
  947. // Exec our match settings
  948. const char *pszExecFile = ( bHighSkill ) ? "server_competitive_rounds_win_conditions_high_skill.cfg" : "server_competitive_rounds_win_conditions.cfg";
  949. if ( bUseStopWatch )
  950. {
  951. pszExecFile = ( bHighSkill ) ? "server_competitive_stopwatch_win_conditions_high_skill.cfg" : "server_competitive_stopwatch_win_conditions.cfg";
  952. }
  953. else if ( bMultiStagePLR || bCTF )
  954. {
  955. pszExecFile = ( bHighSkill ) ? "server_competitive_max_rounds_win_conditions_high_skill.cfg" : "server_competitive_max_rounds_win_conditions.cfg";
  956. }
  957. engine->ServerCommand( CFmtStr( "exec %s\n", pszExecFile ) );
  958. TFGameRules()->SetInStopWatch( bUseStopWatch );
  959. mp_tournament_stopwatch.SetValue( bUseStopWatch );
  960. }
  961. bool ShouldRequestLateJoin() const OVERRIDE
  962. {
  963. auto pTFGameRules = TFGameRules();
  964. if ( !pTFGameRules || !pTFGameRules->IsCompetitiveMode() || pTFGameRules->IsManagedMatchEnded() )
  965. {
  966. return false;
  967. }
  968. const CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  969. int nPlayers = pMatch->GetNumActiveMatchPlayers();
  970. int nMissingPlayers = pMatch->GetCanonicalMatchSize() - nPlayers;
  971. // Allow late-join if we're not started yet, have missing players, and have not lost everyone.
  972. return nMissingPlayers && nPlayers &&
  973. ( pTFGameRules->State_Get() == GR_STATE_BETWEEN_RNDS ||
  974. pTFGameRules->State_Get() == GR_STATE_PREGAME ||
  975. pTFGameRules->State_Get() == GR_STATE_STARTGAME );
  976. }
  977. bool BMatchIsSafeToLeaveForPlayer( const CMatchInfo* pMatchInfo, const CMatchInfo::PlayerMatchData_t *pMatchPlayer ) const OVERRIDE
  978. {
  979. // It's only safe if the match is over
  980. return pMatchInfo->BMatchTerminated();
  981. }
  982. virtual bool BPlayWinMusic( int nWinningTeam, bool bGameOver ) const OVERRIDE
  983. {
  984. if ( bGameOver )
  985. {
  986. TFGameRules()->BroadcastSound( 255, ( nWinningTeam == TF_TEAM_RED ) ? "Announcer.CompMatchWinRed" : "Announcer.CompMatchWinBlu" );
  987. TFGameRules()->BroadcastSound( 255, ( nWinningTeam == TF_TEAM_RED ) ? "MatchMaking.MatchEndRedWinMusic" : "MatchMaking.MatchEndBlueWinMusic" );
  988. }
  989. else
  990. {
  991. if ( nWinningTeam == TF_TEAM_RED )
  992. {
  993. TFGameRules()->BroadcastSound( 255, "Announcer.CompRoundWinRed" );
  994. TFGameRules()->BroadcastSound( 255, "MatchMaking.RoundEndRedWinMusic" );
  995. }
  996. else if ( nWinningTeam == TF_TEAM_BLUE )
  997. {
  998. TFGameRules()->BroadcastSound( 255, "Announcer.CompRoundWinBlu" );
  999. TFGameRules()->BroadcastSound( 255, "MatchMaking.RoundEndBlueWinMusic" );
  1000. }
  1001. else
  1002. {
  1003. TFGameRules()->BroadcastSound( 255, "Announcer.CompRoundStalemate" );
  1004. TFGameRules()->BroadcastSound( 255, "MatchMaking.RoundEndStalemateMusic" );
  1005. }
  1006. }
  1007. return true;
  1008. }
  1009. #endif
  1010. };
  1011. class CCasualMatchGroupDescription : public IMatchGroupDescription
  1012. {
  1013. public:
  1014. class CCasualProgressionDesc : public IProgressionDesc
  1015. {
  1016. public:
  1017. CCasualProgressionDesc( EMatchGroup eMatchGroup )
  1018. : IProgressionDesc( eMatchGroup
  1019. , "models/vgui/12v12_badge.mdl"
  1020. , "resource/ui/PvPCasualRankPanel.res"
  1021. , "TF_Competitive_Level" )
  1022. , m_nLevelsPerStep( 25 )
  1023. , m_nSteps( 6 )
  1024. , m_nAverageXPPerGame( 500 )
  1025. , m_nAverageMinutesPerGame( 30 )
  1026. {
  1027. struct StepInfo_t
  1028. {
  1029. float m_flAvgGamerPerLevel;
  1030. const char* m_pszLevelUpSound;
  1031. };
  1032. const StepInfo_t stepInfo[] = { { 1.5f, "MatchMaking.LevelOneAchieved" }
  1033. , { 2.5f, "MatchMaking.LevelTwoAchieved" }
  1034. , { 4.f, "MatchMaking.LevelThreeAchieved" }
  1035. , { 6.f, "MatchMaking.LevelFourAchieved" }
  1036. , { 9.f, "MatchMaking.LevelFiveAchieved" }
  1037. , { 14.f, "MatchMaking.LevelSixAchieved" } };
  1038. uint32 nNumLevels = m_nLevelsPerStep * m_nSteps;
  1039. uint32 nEndXPForLevel = 0;
  1040. for( uint32 i=0; i<nNumLevels; ++i )
  1041. {
  1042. const uint32 nStep = i / m_nLevelsPerStep;
  1043. LevelInfo_t& level = m_vecLevels[ m_vecLevels.AddToTail() ];
  1044. level.m_nLevelNum = i + 1; // This loop is 0-based, but users will start at level 1
  1045. level.m_nStartXP = nEndXPForLevel; // Use the previous level's end as our start
  1046. // We want the last level to have the same starting and ending XP value so the progress bar looks filled
  1047. // as soon as you hit max level.
  1048. if ( level.m_nLevelNum != nNumLevels )
  1049. {
  1050. nEndXPForLevel += stepInfo[ nStep ].m_flAvgGamerPerLevel * m_nAverageXPPerGame;
  1051. }
  1052. level.m_nEndXP = nEndXPForLevel;
  1053. level.m_pszLevelTitle = NULL; // Casual levels dont have titles
  1054. level.m_pszLevelUpSound = stepInfo[ nStep ].m_pszLevelUpSound;
  1055. level.m_pszLobbyBackgroundImage = "competitive/12v12_background001"; // All the same in casual
  1056. }
  1057. }
  1058. #ifdef CLIENT_DLL
  1059. virtual void SetupBadgePanel( CBaseModelPanel *pModelPanel, const LevelInfo_t& level ) const OVERRIDE
  1060. {
  1061. if ( !pModelPanel )
  1062. return;
  1063. int nLevelIndex = level.m_nLevelNum - 1;
  1064. int nSkin = nLevelIndex / m_nLevelsPerStep;
  1065. int nStarsBodyGroup = ( ( nLevelIndex ) % 5 ) + 1;
  1066. int nBulletsBodyGroup = 0;
  1067. int nPlatesBodyGroup = 0;
  1068. int nBannerBodyGroup = 0;
  1069. switch( ( ( nLevelIndex ) / 5 ) % 5 )
  1070. {
  1071. case 0:
  1072. nBulletsBodyGroup = 0;
  1073. nPlatesBodyGroup = 0;
  1074. nBannerBodyGroup = 0;
  1075. break;
  1076. case 1:
  1077. nBulletsBodyGroup = 1;
  1078. nPlatesBodyGroup = 0;
  1079. nBannerBodyGroup = 0;
  1080. break;
  1081. case 2:
  1082. nBulletsBodyGroup = 2;
  1083. nPlatesBodyGroup = 1;
  1084. nBannerBodyGroup = 0;
  1085. break;
  1086. case 3:
  1087. nBulletsBodyGroup = 3;
  1088. nPlatesBodyGroup = 2;
  1089. nBannerBodyGroup = 1;
  1090. break;
  1091. case 4:
  1092. nBulletsBodyGroup = 4;
  1093. nPlatesBodyGroup = 3;
  1094. nBannerBodyGroup = 1;
  1095. break;
  1096. }
  1097. EnsureBadgePanelModel( pModelPanel );
  1098. int nBody = 0;
  1099. CStudioHdr studioHDR( pModelPanel->GetStudioHdr(), g_pMDLCache );
  1100. ::SetBodygroup( &studioHDR, nBody, ::FindBodygroupByName( &studioHDR, "bullets" ), nBulletsBodyGroup );
  1101. ::SetBodygroup( &studioHDR, nBody, ::FindBodygroupByName( &studioHDR, "plates" ), nPlatesBodyGroup );
  1102. ::SetBodygroup( &studioHDR, nBody, ::FindBodygroupByName( &studioHDR, "banner" ), nBannerBodyGroup );
  1103. ::SetBodygroup( &studioHDR, nBody, ::FindBodygroupByName( &studioHDR, "stars" ), nStarsBodyGroup );
  1104. pModelPanel->SetBody( nBody );
  1105. pModelPanel->SetSkin( nSkin );
  1106. }
  1107. virtual const uint32 GetLocalPlayerLastAckdExperience() const OVERRIDE
  1108. {
  1109. // This is bad and hard-coding a match group. We should just make XP a rating type and these functions
  1110. // should just say "use this rating for XP"/"use this rating for acked XP"
  1111. FixmeMMRatingBackendSwapping();
  1112. #if defined CLIENT_DLL && defined STAGING_ONLY
  1113. if ( tf_test_pvp_rank_xp_change.GetInt() != -1 )
  1114. {
  1115. return tf_test_pvp_rank_xp_change.GetInt();
  1116. }
  1117. #endif
  1118. CSOTFLadderData *pLadderData = GetLocalPlayerLadderData( k_nMatchGroup_Casual_12v12 );
  1119. if ( pLadderData )
  1120. {
  1121. return pLadderData->Obj().last_ackd_experience();
  1122. }
  1123. return 0u;
  1124. }
  1125. virtual const uint32 GetPlayerExperienceBySteamID( CSteamID steamid ) const OVERRIDE
  1126. {
  1127. // This is bad and hard-coding a match group. We should just make XP a rating type and these functions
  1128. // should just say "use this rating for XP"/"use this rating for acked XP"
  1129. FixmeMMRatingBackendSwapping();
  1130. #if defined CLIENT_DLL && defined STAGING_ONLY
  1131. if ( tf_test_pvp_rank_xp_change.GetInt() != -1 )
  1132. {
  1133. return tf_test_pvp_rank_xp_change.GetInt();
  1134. }
  1135. #endif
  1136. #ifndef CLIENT_DLL
  1137. #error This function is only okay on the client due to calling yielding stuff
  1138. #endif
  1139. CSOTFLadderData *pLadderData = YieldingGetPlayerLadderDataBySteamID( steamid, k_nMatchGroup_Casual_12v12 );
  1140. if ( pLadderData )
  1141. {
  1142. return pLadderData->Obj().experience();
  1143. }
  1144. return 0u;
  1145. }
  1146. #endif // CLIENT_DLL
  1147. #if defined GC
  1148. virtual bool BYldAcknowledgePlayerXPOnTransaction( CSQLAccess &transaction,
  1149. CTFSharedObjectCache *pLockedSOCache ) const OVERRIDE
  1150. {
  1151. // This is bad and a result of XP being just a rating in some places but a magic field elsewhere.
  1152. FixmeMMRatingBackendSwapping();
  1153. Assert( GGCTF()->IsSteamIDLockedByCurJob( pLockedSOCache->GetOwner() ) );
  1154. CSOTFLadderData *pLadderData = NULL;
  1155. // Find their ladder data
  1156. CSharedObjectTypeCache *pItemTypeCache = pLockedSOCache->FindTypeCache( CSOTFLadderData::k_nTypeID );
  1157. if ( pItemTypeCache )
  1158. {
  1159. CSOTFLadderData queryItem( pLockedSOCache->GetOwner().GetAccountID(), k_nMatchGroup_Casual_12v12 );
  1160. pLadderData = pLockedSOCache->FindTypedSharedObject< CSOTFLadderData >( queryItem );
  1161. }
  1162. if ( !pLadderData )
  1163. {
  1164. return false;
  1165. }
  1166. // Update the last ack'd to the current
  1167. //
  1168. // XXX(JohnS): This function needs to be destroyed, but if kept, we would probably want to fix
  1169. // CSharedObjectTransactionEx to work properly with multiple SOCaches, such that
  1170. // BYieldingUpdatePlayerRating could work with it, such that this function could just require a
  1171. // sharedobject transaction...
  1172. CSOTFLadderData dataCopy;
  1173. dataCopy.Copy( *pLadderData );
  1174. uint32_t nAck = pLadderData->Obj().experience();
  1175. dataCopy.Obj().set_last_ackd_experience( nAck );
  1176. CUtlVector<int> dirtyFields( 0, 1 );
  1177. dirtyFields.AddToTail( CSOTFLadderPlayerStats::kLastAckdExperienceFieldNumber );
  1178. bool bRet = dataCopy.BYieldingAddWriteToTransaction( transaction, dirtyFields );
  1179. if ( bRet )
  1180. {
  1181. transaction.AddCommitListener( [pLockedSOCache, nAck, pLadderData]() {
  1182. Assert( GGCTF()->IsSteamIDLockedByCurJob( pLockedSOCache->GetOwner() ) );
  1183. pLadderData->Obj().set_last_ackd_experience( nAck );
  1184. pLockedSOCache->DirtyNetworkObject( pLadderData );
  1185. });
  1186. }
  1187. return bRet;
  1188. }
  1189. virtual const bool BRankXPIsActuallyPrimaryMMRating() const OVERRIDE { return false; }
  1190. #endif // defined GC
  1191. #if defined GC_DLL || ( defined STAGING_ONLY && defined CLIENT_DLL )
  1192. virtual void DebugSpewLevels() const OVERRIDE
  1193. {
  1194. Msg( "Spewing casual levels:\n" );
  1195. Msg( "Assuming average %d XP per game and average %d minutes per game\n", m_nAverageXPPerGame, m_nAverageMinutesPerGame );
  1196. uint32 nNumGamesToAchieve = 0;
  1197. // Walk the levels to find where the passed in experience value falls
  1198. for( int i=0; i< m_vecLevels.Count(); ++i )
  1199. {
  1200. nNumGamesToAchieve += ( m_vecLevels[ i ].m_nEndXP - m_vecLevels[ i ].m_nStartXP ) / m_nAverageXPPerGame;
  1201. uint32 nExpectedMinutesToAchieve = nNumGamesToAchieve * m_nAverageMinutesPerGame;
  1202. #ifdef CLIENT_DLL
  1203. int nStep = i / m_nLevelsPerStep;
  1204. const CEconItemRarityDefinition* pRarity = GetItemSchema()->GetRarityDefinition( nStep + 1 );
  1205. vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" );
  1206. vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( scheme );
  1207. Color color = pScheme->GetColor( GetColorNameForAttribColor( pRarity->GetAttribColor() ), Color( 255, 255, 255, 255 ) );
  1208. ConColorMsg( color, "Level %d:\t%d - %d. Expected games required: %d Expected hours required: %.2f\n", m_vecLevels[ i ].m_nLevelNum, m_vecLevels[ i ].m_nStartXP, m_vecLevels[ i ].m_nEndXP, nNumGamesToAchieve, ( nExpectedMinutesToAchieve / 60.f ) );
  1209. #else
  1210. DevMsg( "Level %d:\t%d - %d. Expected games required: %d Expected hours required: %.2f\n", m_vecLevels[ i ].m_nLevelNum, m_vecLevels[ i ].m_nStartXP, m_vecLevels[ i ].m_nEndXP, nNumGamesToAchieve, ( nExpectedMinutesToAchieve / 60.f ) );
  1211. #endif
  1212. }
  1213. }
  1214. #endif
  1215. private:
  1216. const uint32 m_nLevelsPerStep;
  1217. const uint32 m_nSteps;
  1218. const uint32 m_nAverageXPPerGame;
  1219. const uint32 m_nAverageMinutesPerGame;
  1220. };
  1221. CCasualMatchGroupDescription( EMatchGroup eMatchGroup, ConVar* pmm_match_group_size, ConVar* pmm_match_group_size_minimum )
  1222. : IMatchGroupDescription( eMatchGroup
  1223. , { eMatchMode_MatchMaker_LateJoinMatchBased // m_eLateJoinMode;
  1224. , eMMPenaltyPool_Casual // m_ePenaltyPool
  1225. , true // m_bUsesSkillRatings;
  1226. , true // m_bSupportsLowPriorityQueue;
  1227. , true // m_bRequiresMatchID;
  1228. , CASUAL_REQUIRED_SCORE // m_pmm_required_score;
  1229. , true // m_bUseMatchHud;
  1230. , "server_casual.cfg" // m_pszExecFileName;
  1231. , pmm_match_group_size // m_pmm_match_group_size;
  1232. , pmm_match_group_size_minimum // m_pmm_match_group_size_minimum;
  1233. , MATCH_TYPE_CASUAL // m_eMatchType;
  1234. , true // m_bShowPreRoundDoors;
  1235. , true // m_bShowPostRoundDoors;
  1236. , "#TF_Competitive_GameOver" // m_pszMatchEndKickWarning;
  1237. , "MatchMaking.RoundStartCasual" // m_pszMatchStartSound;
  1238. , true // m_bAutoReady;
  1239. , false // m_bShowRankIcons;
  1240. , false // m_bUseMatchSummaryStage;
  1241. , false // m_bDistributePerformanceMedals;
  1242. , true // m_bIsCompetitiveMode;
  1243. , false // m_bUseFirstBlood;
  1244. , false // m_bUseReducedBonusTime;
  1245. , true // m_bUseAutoBalance;
  1246. , false // m_bAllowTeamChange;
  1247. , true // m_bRandomWeaponCrits;
  1248. , false // m_bFixedWeaponSpread;
  1249. , false // m_bRequireCompleteMatch;
  1250. , true // m_bTrustedServersOnly;
  1251. , false // m_bForceClientSettings;
  1252. , false // m_bAllowDrawingAtMatchSummary
  1253. , true // m_bAllowSpecModeChange
  1254. , true // m_bAutomaticallyRequeueAfterMatchEnds
  1255. , true // m_bUsesMapVoteOnRoundEnd
  1256. , true // m_bUsesXP
  1257. , true // m_bUsesDashboardOnRoundEnd
  1258. , true // m_bUsesSurveys
  1259. , false } ) // m_bStrictMatchmakerScoring
  1260. {
  1261. m_pProgressionDesc = new CCasualProgressionDesc( eMatchGroup );
  1262. }
  1263. #ifdef GC_DLL
  1264. virtual EMMRating PrimaryMMRatingBackend() const OVERRIDE
  1265. {
  1266. // Hard-coded for casual at the moment. Live-swapping won't work either, since lobbies don't know what they were
  1267. // formed with in Match_Result
  1268. FixmeMMRatingBackendSwapping();
  1269. return k_nMMRating_12v12_DRILLO;
  1270. }
  1271. virtual const std::vector< EMMRating > &MatchResultRatingBackends() const OVERRIDE
  1272. {
  1273. FixmeMMRatingBackendSwapping(); // Shouldn't be hard-coded for 12v12, param
  1274. static std::vector< EMMRating > casualRatings = { k_nMMRating_12v12_DRILLO, k_nMMRating_12v12_GLICKO };
  1275. return casualRatings;
  1276. }
  1277. // Copy party's casual criteria
  1278. virtual bool InitMatchFromParty( MatchDescription_t* pMatch, const MatchParty_t* pParty ) const OVERRIDE
  1279. {
  1280. pMatch->m_acceptableCasualCriteria.Clear();
  1281. pMatch->m_acceptableCasualCriteria.CopyFrom( pParty->m_casualCriteria );
  1282. CCasualCriteriaHelper helper( pMatch->m_acceptableCasualCriteria );
  1283. return helper.AnySelected();
  1284. }
  1285. virtual bool InitMatchFromLobby( MatchDescription_t* pMatch, CTFLobby* pLobby ) const OVERRIDE
  1286. {
  1287. pMatch->m_acceptableCasualCriteria.Clear();
  1288. CCasualCriteriaHelper helper( pMatch->m_acceptableCasualCriteria );
  1289. const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByName( pLobby->GetMapName() );
  1290. if ( pMap )
  1291. {
  1292. helper.SetMapSelected( pMap->m_nDefIndex, true );
  1293. }
  1294. pMatch->m_acceptableCasualCriteria = helper.GetCasualCriteria();
  1295. return helper.AnySelected();
  1296. }
  1297. // Set the match party's criteria to the TFParty's criteria
  1298. virtual void SyncMatchParty( const CTFParty *pParty, MatchParty_t *pMatchParty ) const OVERRIDE
  1299. {
  1300. pMatchParty->m_casualCriteria.CopyFrom( pParty->Obj().search_casual() );
  1301. }
  1302. // Get all the valid categories and randomly choose a category to play
  1303. virtual void SelectModeSpecificParameters( const MatchDescription_t* pMatch, CTFLobby* pLobby ) const OVERRIDE
  1304. {
  1305. if ( *tf_mm_ladder_force_map_by_name.GetString() )
  1306. {
  1307. pLobby->SetMapName( tf_mm_ladder_force_map_by_name.GetString() );
  1308. return;
  1309. }
  1310. CUtlVector< const MapDef_t* > vecValidMaps;
  1311. int nNumMaps = GetItemSchema()->GetMasterMapsList().Count();
  1312. CCasualCriteriaHelper helper( pMatch->m_acceptableCasualCriteria );
  1313. for( int i=0; i < nNumMaps; ++ i )
  1314. {
  1315. const MapDef_t* pMapDef = GetItemSchema()->GetMasterMapsList()[ i ];
  1316. if ( helper.IsMapSelected( pMapDef ) )
  1317. {
  1318. vecValidMaps.AddToTail( pMapDef );
  1319. }
  1320. }
  1321. if ( vecValidMaps.Count() == 0 )
  1322. {
  1323. // We should have SOMETHING valid.
  1324. Assert( vecValidMaps.Count() > 0 );
  1325. pLobby->SetMapName( "ctf_2fort" ); // Everybody loves 2fort
  1326. return;
  1327. }
  1328. const char *pszMap = vecValidMaps[ RandomInt( 0, vecValidMaps.Count() - 1 ) ]->pszMapName;
  1329. pLobby->SetMapName( pszMap );
  1330. }
  1331. // Private helper to check an entire party's join permissions and criteria, for both PartyCompatible and Intersect
  1332. // below, since they do the same computation, one just keeps it.
  1333. bool BThreadedCheckPartyAllowedToJoinMatchAndIntersectCriteria( const MatchDescription_t* pMatch,
  1334. const MatchParty_t *pParty,
  1335. CCasualCriteriaHelper &criteria ) const
  1336. {
  1337. if ( *tf_mm_ladder_force_map_by_name.GetString() )
  1338. {
  1339. // If a map is forced, we don't care about compatibility. We're obviously testing something.
  1340. return true;
  1341. }
  1342. // Check for blacklisted former members that tank compatibility
  1343. if ( !BCheckLeftMatchMembersAgainstParty( pMatch, pParty, &BThreadedFormerMemberMayRejoinJoinCasualOrMvMMatch) )
  1344. { return false; }
  1345. // Intersect criteria with new party
  1346. criteria = CCasualCriteriaHelper( pMatch->m_acceptableCasualCriteria );
  1347. criteria.Intersect( pParty->m_casualCriteria );
  1348. return criteria.AnySelected();
  1349. }
  1350. virtual bool BThreadedPartyCompatibleWithMatch( const MatchDescription_t* pMatch, const MatchParty_t *pCandidateParty ) const OVERRIDE
  1351. {
  1352. CCasualCriteriaHelper helper( pMatch->m_acceptableCasualCriteria );
  1353. return BThreadedCheckPartyAllowedToJoinMatchAndIntersectCriteria( pMatch, pCandidateParty, helper );
  1354. }
  1355. virtual bool BThreadedIntersectMatchWithParty( MatchDescription_t* pMatch, const MatchParty_t* pParty ) const OVERRIDE
  1356. {
  1357. CCasualCriteriaHelper helper( pMatch->m_acceptableCasualCriteria );
  1358. bool bRet = BThreadedCheckPartyAllowedToJoinMatchAndIntersectCriteria( pMatch, pParty, helper );
  1359. // Update the match
  1360. if ( bRet )
  1361. {
  1362. pMatch->m_acceptableCasualCriteria = helper.GetCasualCriteria();
  1363. return true;
  1364. }
  1365. return false;
  1366. }
  1367. virtual bool BThreadedPartiesCompatible( const MatchParty_t *pLeftParty, const MatchParty_t *pRightParty ) const OVERRIDE
  1368. {
  1369. CCasualCriteriaHelper helper( pLeftParty->m_casualCriteria );
  1370. helper.Intersect( pRightParty->m_casualCriteria );
  1371. return helper.AnySelected();
  1372. }
  1373. virtual void GetServerDetails( const CMsgGameServerMatchmakingStatus& msg, int& nChallengeIndex, const char* pszMap ) const OVERRIDE
  1374. {}
  1375. virtual const char* GetUnauthorizedPartyReason( CTFParty* pParty ) const OVERRIDE
  1376. {
  1377. // Don't need no credit card to ride this train
  1378. return NULL;
  1379. }
  1380. virtual void Dump( const char *pszLeader, int nSpewLevel, int nLogLevel, const MatchParty_t* pMatch ) const OVERRIDE
  1381. {
  1382. CUtlString strSelectedMaps;
  1383. CUtlVector< const MapDef_t* > vecValidMaps;
  1384. int nCount = 0;
  1385. int nNumMaps = GetItemSchema()->GetMasterMapsList().Count();
  1386. CCasualCriteriaHelper helper( pMatch->m_casualCriteria );
  1387. for( int i=0; i < nNumMaps; ++ i )
  1388. {
  1389. const MapDef_t* pMapDef = GetItemSchema()->GetMasterMapsList()[ i ];
  1390. if ( helper.IsMapSelected( pMapDef ) )
  1391. {
  1392. if ( nCount > 0 )
  1393. {
  1394. strSelectedMaps += ", ";
  1395. }
  1396. strSelectedMaps += pMapDef->pszMapName;
  1397. ++nCount;
  1398. }
  1399. }
  1400. EmitInfo( SPEW_GC, nSpewLevel, nLogLevel, "%s Search casual maps: %s\n", pszLeader, strSelectedMaps.String() );
  1401. EmitInfo( SPEW_GC, nSpewLevel, nLogLevel, "%s Best casual server ping: %.0fms\n", pszLeader, pMatch->m_flPingClosestServer );
  1402. }
  1403. #endif
  1404. #ifdef CLIENT_DLL
  1405. virtual bool BGetRoundStartBannerParameters( int& nSkin, int& nBodyGroup ) const OVERRIDE
  1406. {
  1407. nBodyGroup = 0;
  1408. nSkin = TFGameRules()->GetRoundsPlayed();
  1409. return true;
  1410. }
  1411. virtual bool BGetRoundDoorParameters( int& nSkin, int& nLogoBodyGroup ) const OVERRIDE
  1412. {
  1413. nLogoBodyGroup = 1;
  1414. nSkin = 3;
  1415. return true;
  1416. }
  1417. virtual const char *GetMapLoadBackgroundOverride( bool bWidescreen ) const OVERRIDE
  1418. {
  1419. return NULL;
  1420. }
  1421. #endif
  1422. #ifdef GAME_DLL
  1423. virtual void PostMatchClearServerSettings() const OVERRIDE
  1424. {
  1425. Assert( TFGameRules() );
  1426. TFGameRules()->MatchSummaryEnd();
  1427. }
  1428. virtual void InitGameRulesSettings() const OVERRIDE
  1429. {
  1430. TFGameRules()->SetAllowBetweenRounds( true );
  1431. }
  1432. virtual void InitGameRulesSettingsPostEntity() const OVERRIDE
  1433. {
  1434. CTeamControlPointMaster *pMaster = ( g_hControlPointMasters.Count() ) ? g_hControlPointMasters[0] : NULL;
  1435. bool bMultiStagePLR = ( tf_gamemode_payload.GetBool() && pMaster && pMaster->PlayingMiniRounds() &&
  1436. pMaster->GetNumRounds() > 1 && TFGameRules()->HasMultipleTrains() );
  1437. bool bCTF = tf_gamemode_ctf.GetBool();
  1438. bool bUseStopWatch = TFGameRules()->MatchmakingShouldUseStopwatchMode();
  1439. // Exec our match settings
  1440. const char *pszExecFile = bUseStopWatch ? "server_casual_stopwatch_win_conditions.cfg" :
  1441. ( ( bMultiStagePLR || bCTF ) ? "server_casual_max_rounds_win_conditions.cfg" : "server_casual_rounds_win_conditions.cfg" );
  1442. if ( TFGameRules()->IsPowerupMode() )
  1443. {
  1444. pszExecFile = "server_casual_max_rounds_win_conditions_mannpower.cfg";
  1445. }
  1446. engine->ServerCommand( CFmtStr( "exec %s\n", pszExecFile ) );
  1447. // leave stopwatch off for now
  1448. TFGameRules()->SetInStopWatch( false );//bUseStopWatch );
  1449. mp_tournament_stopwatch.SetValue( false );//bUseStopWatch );
  1450. }
  1451. bool ShouldRequestLateJoin() const OVERRIDE
  1452. {
  1453. auto pTFGameRules = TFGameRules();
  1454. if ( !pTFGameRules || !pTFGameRules->IsCompetitiveMode() || pTFGameRules->IsManagedMatchEnded() )
  1455. {
  1456. Assert( false );
  1457. return false;
  1458. }
  1459. if ( pTFGameRules->BIsManagedMatchEndImminent() )
  1460. return false;
  1461. const CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  1462. int nPlayers = pMatch->GetNumActiveMatchPlayers();
  1463. int nMissingPlayers = pMatch->GetCanonicalMatchSize() - nPlayers;
  1464. // Allow late-join if we have missing players, have not lost everyone
  1465. return nMissingPlayers && nPlayers;
  1466. }
  1467. bool BMatchIsSafeToLeaveForPlayer( const CMatchInfo* pMatchInfo, const CMatchInfo::PlayerMatchData_t *pMatchPlayer ) const
  1468. {
  1469. return true;
  1470. }
  1471. virtual bool BPlayWinMusic( int nWinningTeam, bool bGameOver ) const OVERRIDE
  1472. {
  1473. // Custom for game over
  1474. if ( bGameOver )
  1475. {
  1476. TFGameRules()->BroadcastSound( TF_TEAM_RED, nWinningTeam == TF_TEAM_RED ? "MatchMaking.MatchEndWinMusicCasual" : "MatchMaking.MatchEndLoseMusicCasual" );
  1477. TFGameRules()->BroadcastSound( TF_TEAM_BLUE, nWinningTeam == TF_TEAM_BLUE ? "MatchMaking.MatchEndWinMusicCasual" : "MatchMaking.MatchEndLoseMusicCasual" );
  1478. return true;
  1479. }
  1480. else
  1481. {
  1482. // Let non-match logic handle round wins
  1483. return false;
  1484. }
  1485. }
  1486. #endif
  1487. };
  1488. #if defined STAGING_ONLY && defined CLIENT_DLL
  1489. CON_COMMAND( spew_match_group_levels, "Spew all casual levels" )
  1490. {
  1491. if ( args.ArgC() < 2 )
  1492. return;
  1493. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( (EMatchGroup)atoi( args[1] ) );
  1494. if ( !pMatchDesc || !pMatchDesc->m_pProgressionDesc )
  1495. return;
  1496. pMatchDesc->m_pProgressionDesc->DebugSpewLevels();
  1497. }
  1498. #endif
  1499. const IMatchGroupDescription* GetMatchGroupDescription( const EMatchGroup& eGroup )
  1500. {
  1501. static CMvMMatchGroupDescription descBootcamp ( k_nMatchGroup_MvM_Practice, "server_bootcamp.cfg", /* bTrustedOnly */ false );
  1502. static CMvMMatchGroupDescription descMannup ( k_nMatchGroup_MvM_MannUp, "server_mannup.cfg", /* bTrustedOnly */ true );
  1503. static CLadderMatchGroupDescription descLadder6v6 ( k_nMatchGroup_Ladder_6v6, &tf_mm_match_size_ladder_6v6 );
  1504. static CLadderMatchGroupDescription descLadder9v9 ( k_nMatchGroup_Ladder_9v9, &tf_mm_match_size_ladder_9v9 );
  1505. static CLadderMatchGroupDescription descLadder12v12 ( k_nMatchGroup_Ladder_12v12, &tf_mm_match_size_ladder_12v12 );
  1506. static CCasualMatchGroupDescription descCasual6v6 ( k_nMatchGroup_Casual_6v6, &tf_mm_match_size_ladder_6v6, NULL );
  1507. static CCasualMatchGroupDescription descCasual9v9 ( k_nMatchGroup_Casual_9v9, &tf_mm_match_size_ladder_9v9, NULL );
  1508. static CCasualMatchGroupDescription descCasual12v12 ( k_nMatchGroup_Casual_12v12, &tf_mm_match_size_ladder_12v12, \
  1509. &tf_mm_match_size_ladder_12v12_minimum );
  1510. switch( eGroup )
  1511. {
  1512. case k_nMatchGroup_MvM_Practice:
  1513. return &descBootcamp;
  1514. case k_nMatchGroup_MvM_MannUp:
  1515. return &descMannup;
  1516. case k_nMatchGroup_Ladder_6v6:
  1517. return &descLadder6v6;
  1518. case k_nMatchGroup_Ladder_9v9:
  1519. return &descLadder9v9;
  1520. case k_nMatchGroup_Ladder_12v12:
  1521. return &descLadder12v12;
  1522. case k_nMatchGroup_Casual_6v6:
  1523. return &descCasual6v6;
  1524. case k_nMatchGroup_Casual_9v9:
  1525. return &descCasual9v9;
  1526. case k_nMatchGroup_Casual_12v12:
  1527. return &descCasual12v12;
  1528. default:
  1529. #ifdef GC_DLL
  1530. // We're going to expectedly hit this many times on the client/server.
  1531. // Only complain on the GC
  1532. Assert( false );
  1533. #endif
  1534. break;
  1535. }
  1536. return NULL;
  1537. }
  1538. // If you add a matchmaking group, handle it in the above switch
  1539. COMPILE_TIME_ASSERT( k_nMatchGroup_Casual_12v12 == ( k_nMatchGroup_Count - 1 ) );