/**
 *	Matchmaking for a standard game mode
 */
#Extends "Modes/ModeBase2.Script.txt"

#Const MB_MM_Version			"2018-02-15"
#Const MB_MM_ScriptName	"Modes/ModeMatchmaking2.Script.txt"

// ---------------------------------- //
// Libraries
// ---------------------------------- //
#Include "TextLib" as MM_TL
#Include "Libs/Nadeo/MatchmakingCommon.Script.txt" as MMCommon
#Include "Libs/Nadeo/MatchmakingLobby.Script.txt" as MMLobby
#Include "Libs/Nadeo/MatchmakingMatch.Script.txt" as MMMatch
#Include "Libs/Nadeo/VoteMap.Script.txt" as VoteMap

// ---------------------------------- //
// Settings
// ---------------------------------- //
#Setting S_MatchmakingAPIUrl				"https://v4.live.maniaplanet.com/ingame/public/matchmaking"	as "<hidden>"	///< URL of the matchmaking API
#Setting S_MatchmakingMatchServers	""			as "<hidden>" ///< A comma separated list of match servers logins
#Setting S_MatchmakingMode					0				as "<hidden>"	///< 0 = Off, 1 = Lobby, 2 = Match, 3 = Universal lobby, 4 = Universal match
#Setting S_MatchmakingRematchRatio	-1.			as "<hidden>"	///< Minimum ratio of players agreeing for a rematch to occur
#Setting S_MatchmakingRematchNbMax	2				as "<hidden>"	///< Maxium number of consecutive rematch
#Setting S_MatchmakingVoteForMap		False		as "<hidden>"	///< Allow players to vote for the next played map
#Setting S_MatchmakingProgressive	False		as "<hidden>"	///< Can start a match with less players than the required number
#Setting S_MatchmakingWaitingTime	20			as "<hidden>"	///< Waiting time at the beginning of the matches
#Setting S_MatchmakingEnablePenalty True		as "<hidden>" ///< Enable the penalty for leaving a match
#Setting S_LobbyRoundPerMap				60			as "<hidden>"	///< "Nb of rounds per map in lobby mode")
#Setting S_LobbyMatchmakerPerRound	6				as "<hidden>"	///< Number of matchmaker call in a "King of the Lobby" round
#Setting S_LobbyMatchmakerWait			2				as "<hidden>"	///< Waiting time before the next call to the matchmaking function
#Setting S_LobbyMatchmakerTime			8				as "<hidden>"	///< Time allocated to the matchmaking (seconds)
#Setting S_LobbyDisplayMasters			True		as "<hidden>"	///< Display the masters list
#Setting S_LobbyDisableUI					False		as "<hidden>"	///< Disable lobby UI
#Setting S_LobbyAggressiveTransfer True		as "<hidden>" ///< Enable or disable the aggressive transfert mechanism
#Setting S_KickTimedOutPlayers			True		as "<hidden>"	///< Kick timed out players
#Setting S_MatchmakingErrorMessage	_("An error occured in the matchmaking API. If the problem persist please try to contact this server administrator.") as "<hidden>" ///< Send a message in the chat if an error occured
#Setting S_MatchmakingLogAPIError	False		as "<hidden>"	///< Log the API requests errors
#Setting S_MatchmakingLogAPIDebug	False		as "<hidden>"	///< Log all the api requests and responses
#Setting S_MatchmakingLogMiscDebug	False		as "<hidden>"	///< Log different kinds of debug info
#Setting S_ProgressiveActivation_WaitingTime			180000	as "<hidden>"	///< Average waiting time before progressive matchmaking activate
#Setting S_ProgressiveActivation_PlayersNbRatio	1				as "<hidden>"	///< Multiply the required players nb by this, if there's less player in the lobby activate progressive

// ---------------------------------- //
// Constants
// ---------------------------------- //
#Const C_Lobby_Private_BotsNb 0 ///< Number of bots in the lobby
#Const C_Match_Private_RematchWaitingTime 15000 ///< Waiting time at the beginning of a new map without a new matchmaking session

// ---------------------------------- //
// Globales
// ---------------------------------- //
declare Text MM_Private_MasterLogin; ///< Login of the match master
declare Integer MM_Private_RematchNb; ///< Number of rematch played
declare Boolean MM_Private_CreateNewSession; ///< Start a new match session or continue the previous one
declare Boolean MM_Private_SessionClosed; ///< Was the session closed?
declare Integer MM_FakeStartTime; //< Fake start time for Trackmania

declare Boolean MB_Settings_UseMatchmakingImplementationHelper;
declare Boolean MB_Settings_UseDefaultMatchmaking;

// ---------------------------------- //
// Extends
// ---------------------------------- //
// ---------------------------------- //
// Common extends
// ---------------------------------- //
***MB_Private_LogVersions***
***
Log::RegisterScript(MB_MM_ScriptName, MB_MM_Version);
Log::RegisterScript(MMCommon::GetScriptName(), MMCommon::GetScriptVersion());
Log::RegisterScript(MMLobby::GetScriptName(), MMLobby::GetScriptVersion());
Log::RegisterScript(MMMatch::GetScriptName(), MMMatch::GetScriptVersion());
Log::RegisterScript(VoteMap::GetScriptName(), VoteMap::GetScriptVersion());
***

***MB_Private_Matchmaking***
***
declare MB_Private_UseMatchmaking = (S_MatchmakingMode > 0);
MMCommon::MB_InitSettingMatchmakingMode(S_MatchmakingMode);
if (
	S_MatchmakingMode == MMCommon::MatchmakingMode_Lobby() ||
	S_MatchmakingMode == MMCommon::MatchmakingMode_UniversalLobby()
) {
	MB_Private_SetExtendMode(C_ExtendMode_Lobby);
}
***

***MB_Private_Settings***
***
MB_Settings_UseMatchmakingImplementationHelper = True;
MB_Settings_UseDefaultMatchmaking = True;
***

***MB_Private_LoadLibraries***
***
if (MB_Private_UseMatchmaking) {
	MMCommon::Load();
	MMCommon::SetMode(S_MatchmakingMode);
	MMCommon::SetApiUrl(S_MatchmakingAPIUrl);
	MMCommon::SetProgressiveMatchmaking(S_MatchmakingProgressive);
	MMCommon::SetErrorMessage(S_MatchmakingErrorMessage);
	MMCommon::SetLogDisplay("APIError", S_MatchmakingLogAPIError);
	MMCommon::SetLogDisplay("APIDebug", S_MatchmakingLogAPIDebug);
	MMCommon::SetLogDisplay("MiscDebug", S_MatchmakingLogMiscDebug);
	MMCommon::SetChannelServer(S_IsChannelServer);
} else {
	MMCommon::SetMode(S_MatchmakingMode);
	if (ServerAdmin != Null) ServerAdmin.SetLobbyInfo(False, AllPlayers.count, 0, 0.);
}
***

***MB_Private_StartServer***
***
if (MB_Private_UseMatchmaking && MB_Settings_UseMatchmakingImplementationHelper) {
	if (MMCommon::GetApiUrl() == "") {
		Log::Error("[Matchmaking] You must set a matchmaking API URL in your #Settings.");
	}
	if (MMCommon::GetMatchFormat().count <= 0) {
		Log::Error("[Matchmaking] You must define a match format. Use MM_SetFormat() inside ***MM_SetupMatchmaking***.");
	}
}
***

***MB_Private_EnableIntroSequence***
***
/*
 *	Disable intro during a match because it is
 *	replaced by a waiting time for the players
 *	and during lobby because there's already
 *	a rolling background intro
 */
if (MB_Private_UseMatchmaking) {
	MB_Private_EnableIntroSequence = False;
} else {
	MB_Private_EnableIntroSequence = True;
}
***

***MB_Private_EnableLadder***
***
// Disable the ladder in the lobby
if (MB_Private_UseMatchmaking && MMCommon::IsLobbyServer()) {
	MB_Private_EnableLadder = False;
}
***

***MB_Private_EnablePodiumSequence***
***
// Disable the podium sequence in the lobby
if (MB_Private_UseMatchmaking && MMCommon::IsLobbyServer()) {
	MB_Private_EnablePodiumSequence = False;
}
***

***MB_Private_EnableChannelProgression***
***
// Disable the channel progression sequence in the lobby
if (MB_Private_UseMatchmaking && MMCommon::IsLobbyServer()) {
	MB_Private_EnableChannelProgression = False;
}
***

***MB_Private_UnloadLibraries***
***
if (MB_Private_UseMatchmaking) {
	MMCommon::Unload();
}
***

***MB_Private_Yield***
***
if (MMCommon::MB_SettingMatchmakingModeUpdated(S_MatchmakingMode)) {
	MB_Private_RestartScript();
}

// Skip yield if we're not on a matchmaking server
if (MMCommon::IsMatchmakingServer()) {
	// Lobby
	if (MMCommon::IsLobbyServer()) {
		Message::Loop();
		
		declare MM_Private_NeedAlliesUpdate = MMLobby::MM_PlayLoop();
		declare MM_Private_NeedLoadAllies = False;
		
		foreach (Event in PendingEvents) {
			if (MMCommon::IsEventType(Event, CSmModeEvent::EType::OnPlayerRemoved)) {
				MM_Private_NeedAlliesUpdate = True;
				MMLobby::SaveAllies(Event.User);
				MMLobby::UpdatePlayersList();
				MMLobby::CancelMatch(Event.User);
			} else if (MMCommon::IsEventType(Event, CSmModeEvent::EType::OnPlayerAdded)) {
				MM_Private_NeedAlliesUpdate = True;
				MM_Private_NeedLoadAllies = True;
				MMLobby::UpdatePlayersList();
			}
		}
		
		if (MM_Private_NeedLoadAllies) {
			MMLobby::LoadAllies();
		}
		if (MM_Private_NeedAlliesUpdate || MM_Private_NeedLoadAllies) {
			MMLobby::ComputeAllies();
		}
	}
	// Match
	else if (MMCommon::IsMatchServer()) {
		foreach (Event in PendingEvents) {
			if (MMCommon::IsEventType(Event, CSmModeEvent::EType::OnPlayerRemoved)) {
				if (!Event.PlayerWasInLadderMatch && Event.User != Null && Event.User.Id != NullId) {
					MMMatch::AddKickedPlayer(Event.User.Login);
				}
			}
		}
		MMMatch::Ping(False);
		MMMatch::ManagePlayers();
		
		// Check the number of players on the match server
		// If there's not any players left on the server, restart the matchmaking
		declare MM_Private_Restart = MMMatch::MM_CheckPlayersNumbers();
		if (MM_Private_Restart) MB_Private_StopMatch();
	}
	
	// ---------------------------------- //
	// Manage the HTTP response from the API
	declare Ident[] MM_Private_ToRemove;
	foreach (Request in Http.Requests) {
		if (!MMCommon::IsInPendingRequests(Request.Id)) continue;
		
		if (Request.IsCompleted) {
			MM_Private_ToRemove.add(Request.Id);
		}
	}
	
	// Destroy the completed requests
	foreach (RequestId in MM_Private_ToRemove) {
		if (!Http.Requests.existskey(RequestId)) continue;
		declare Request <=> Http.Requests[RequestId];
		
		// Success
		if (Request.StatusCode >= 200 && Request.StatusCode < 300) {
			declare RequestType = MMCommon::GetPendingRequestType(Request.Id);
			switch (RequestType) {
				case MMCommon::RequestType_GetPlayers(): {
					MMLobby::SetupPlayer(Request.Result);
				}
				case MMCommon::RequestType_GetMatches(): {
					if (MMMatch::GetMatchStatus() == MMMatch::MatchStatus_Waiting()) {
						MMMatch::SetupMatch(Request.Result);
					} else {
						MMMatch::SetupSubstitute(Request.Result);
					}
				}
				default: {
					if (MMCommon::GetLogDisplay("APIDebug")) {
						Log::Log("[API] Response: "^Request.Url^"\nStatus : "^Request.StatusCode^"\nResult : "^Request.Result);
					}
				}
			}
		}
		// Fail
		else {
			if (MMCommon::GetLogDisplay("APIError")) {
				if (Request.StatusCode == 401) {
					Log::Log("[ERROR] Matchmaking HTTP API Error 401. Waiting for your server to register on Nadeo master server.");
				} else if (Request.StatusCode == 404) {
					Log::Log("[ERROR] Matchmaking HTTP API Error 404. Maybe the URL in the setting is wrong.");
				} else {
					Log::Log("[ERROR] Matchmaking HTTP Error "^Request.StatusCode^".");
				}
			}
			if (MMCommon::GetErrorMessage() != "") UIManager.UIAll.SendChat(MM_TL::Compose("%1 %2", MMCommon::GetMessagePrefix(), MMCommon::GetErrorMessage()));
		}
			
		MMCommon::RemovePendingRequest(RequestId);
		Http.Destroy(Http.Requests[RequestId]);
	}
}
***

// ---------------------------------- //
// Lobby extends
// ---------------------------------- //
***Lobby_Settings***
***
MB_Settings_UseDefaultLadder = False;
MB_Settings_UseDefaultIntroSequence = False;
MB_Settings_UseDefaultPodiumSequence = False;
MB_Settings_UseDefaultChannelProgression = False;
MB_Settings_UseDefaultUIManagement = False;
***

***Lobby_LoadLibraries***
***
if (MB_Private_UseMatchmaking && MMCommon::IsLobbyServer()) {
	MMLobby::Load();
	MMLobby::SetMatchServers(S_MatchmakingMatchServers);
	MB_Settings_UseDefaultHud = True;
	if (ServerAdmin != Null) ServerAdmin.SetLobbyInfo(True, AllPlayers.count, 255, 0.);
}
***

***Lobby_InitServer***
***
declare MM_Private_PrevLobbyDisplayMasters = True;
***

***Lobby_StartServer***
***
MMLobby::MM_StartServer(S_LobbyDisableUI, S_KickTimedOutPlayers);

// Load rules manialink
declare MM_Private_MLRules = MM_Private_GetMatchRulesManialink();
declare MM_Private_DisplayRules = True;
if (MM_Private_MLRules == "") MM_Private_DisplayRules = False;

// Create UI
Layers::Create("MM_Private_GaugeTimer", MMLobby::GetMLGaugeTimer());
Layers::Create("MM_Private_LobbyScreen", MMLobby::GetMLLobbyScreen(MM_Private_DisplayRules));
Layers::Create("MM_Private_PlayersList", MMLobby::GetMLPlayersList());
Layers::Create("MM_Private_MastersList", MMLobby::GetMLMastersList());
Layers::Create("MM_Private_Header", MMLobby::GetMLHeader());
Layers::Create("MM_Private_Rules", MM_Private_MLRules);
Layers::Create("MM_Private_Versus", MMLobby::GetMLVersus());
Layers::Create("MM_Private_Substitute", MMLobby::GetMLSubstitute());
Layers::Create("MM_Private_Reconnect", MMLobby::GetMLReconnect());
Layers::Attach("MM_Private_GaugeTimer");
Layers::Attach("MM_Private_LobbyScreen");
Layers::Attach("MM_Private_PlayersList");
Layers::Attach("MM_Private_MastersList");
Layers::Attach("MM_Private_Header");
Layers::Attach("MM_Private_Rules");
Layers::Attach("MM_Private_Versus");
Layers::Attach("MM_Private_Substitute");
Layers::Attach("MM_Private_Reconnect");
Layers::SetType("MM_Private_GaugeTimer", CUILayer::EUILayerType::CutScene);
Layers::SetType("MM_Private_LobbyScreen", CUILayer::EUILayerType::CutScene);
Layers::SetType("MM_Private_PlayersList", CUILayer::EUILayerType::CutScene);
Layers::SetType("MM_Private_MastersList", CUILayer::EUILayerType::CutScene);
Layers::SetType("MM_Private_Header", CUILayer::EUILayerType::CutScene);
Layers::SetType("MM_Private_Rules", CUILayer::EUILayerType::CutScene);
Layers::SetType("MM_Private_Versus", CUILayer::EUILayerType::CutScene);
Layers::SetType("MM_Private_Substitute", CUILayer::EUILayerType::CutScene);
Layers::SetType("MM_Private_Reconnect", CUILayer::EUILayerType::CutScene);
***

***Lobby_StartMap***
***
MMLobby::MM_StartMap();

// Initialize bots
Users_SetNbFakeUsers(C_Lobby_Private_BotsNb, 0);

// Initialize timers
MM_Private_SetStartTime(Now + 3000);
MM_Private_SetEndTime(-1, False);
***

***Lobby_BeforeLoadMap***
***
// Synchro client and server state
MMLobby::Synchro();
***

***Lobby_InitRound***
***
declare MM_Private_MatchmakingRunNb = 0;
***

***Lobby_StartRound***
***
MMLobby::MM_StartRound(S_LobbyMatchmakerWait, S_LobbyMatchmakerTime, S_LobbyRoundPerMap);
***

***Lobby_PlayLoop***
***
MMLobby::UpdateUI();

if (S_LobbyAggressiveTransfer) {
	MMLobby::AggressiveTransfert();
}

// Display masters
if (MM_Private_PrevLobbyDisplayMasters != S_LobbyDisplayMasters) {
	MM_Private_PrevLobbyDisplayMasters = S_LobbyDisplayMasters;
	Layers::SetVisibility("MM_Private_MastersList", S_LobbyDisplayMasters);
}

// Manage XmlRpc events
declare MM_Private_ActionsToExecute = MMLobby::MM_ManageXmlRpcEvents();
foreach (Action in MM_Private_ActionsToExecute) {
	switch (Action) {
		case "StartMatchmaker": MMLobby::MatchmakerStart(S_ProgressiveActivation_WaitingTime, S_ProgressiveActivation_PlayersNbRatio);
	}
}

// Change phase
switch (MMLobby::GetLobbyPhase()) {
	// ---------------------------------- //
	// Start Matchmaking
	case MMLobby::LobbyPhase_Playing(): {
		if (MMLobby::GetLobbyEndTime() > 0 && Now > MMLobby::GetLobbyEndTime()) {
			MMLobby::SetLobbyPhase(MMLobby::LobbyPhase_Matchmaking());
			Message::CleanAllMessages();
			Lobby_Private_InitPlayers();
			MM_Private_MatchmakingRunNb += 1;
			
			if (MM_Private_MatchmakingRunNb >= S_LobbyMatchmakerPerRound) {
				// ---------------------------------- //
				// Find the winner
				Lobby_Private_FindWinner();
				
				// ---------------------------------- //
				// Announce the winner
				Lobby_Private_AnnounceWinner();
			}
			
			// ---------------------------------- //
			// Start matchmaking
			if (!S_LobbyDisableUI) {
				declare MM_Private_Message = _("Matchmaking in progress...");
				if (!MMLobby::MatchmakingIsEnabled()) MM_Private_Message = _("Matchmaking disabled.");
				foreach (Player in AllPlayers) {
					if (!MMLobby::IsDisplayingRulesOrQuiz(Player)) {
						Message::SendStatusMessage(
							Player,
							MM_Private_Message,
							S_LobbyMatchmakerTime * 1000,
							1
						);
					} else {
						Message::CleanStatusMessages(Player);
					}
				}
			}
			
			MMLobby::SetTimersAutoDown(S_LobbyMatchmakerTime * 1000);
			MMLobby::SetLobbyEndTime(Now + (S_LobbyMatchmakerTime * 1000));
			
			MMLobby::ComputeAllies();
			MMLobby::MatchmakerStart(S_ProgressiveActivation_WaitingTime, S_ProgressiveActivation_PlayersNbRatio);
		}
	}
	// ---------------------------------- //
	// Start playing
	case MMLobby::LobbyPhase_Matchmaking(): {
		MMLobby::MatchmakerRun();
			
		if (MMLobby::GetLobbyEndTime() > 0 && Now > MMLobby::GetLobbyEndTime()) {
			MMLobby::MatchmakerStop();
			Message::CleanAllMessages();
			
			if (MM_Private_MatchmakingRunNb >= S_LobbyMatchmakerPerRound) {
				if (MB_Private_SectionCount_Round >= S_LobbyRoundPerMap) {
					MB_StopMap();
				} else {
					MB_StopRound();
				}
			} else {
				MMLobby::SetLobbyPhase(MMLobby::LobbyPhase_Playing());
				MMLobby::SetLobbyStartTime(Now);
				MMLobby::SetLobbyEndTime(MMLobby::GetLobbyStartTime() + (S_LobbyMatchmakerWait * 1000));
				MMLobby::UpdateTimers();
			}
		}
	}
}
***

***Lobby_EndRound***
***
MMLobby::MM_EndRound();
***

***Lobby_EndMap***
***
MMLobby::MM_EndMap();

Lobby_Private_UnspawnPlayers();

Message::SendBigMessage(_("Going to next map"), 3000, 1, CUIConfig::EUISound::EndMatch, 0);
MB_Private_Sleep(3000);
Message::CleanAllMessages();
MB_Private_Sleep(500);
***

***Lobby_EndServer***
***
MMLobby::MM_EndServer();

Layers::Destroy("MM_Private_GaugeTimer");
Layers::Destroy("MM_Private_LobbyScreen");
Layers::Destroy("MM_Private_PlayersList");
Layers::Destroy("MM_Private_MastersList");
Layers::Destroy("MM_Private_Header");
Layers::Destroy("MM_Private_Rules");
Layers::Destroy("MM_Private_Versus");
Layers::Destroy("MM_Private_Substitute");
Layers::Destroy("MM_Private_Reconnect");
***

***Lobby_UnloadLibraries***
***
if (MB_Private_UseMatchmaking && MMCommon::IsLobbyServer()) {
	MMLobby::Unload();
}
***

// ---------------------------------- //
// Match extends
***Match_LoadLibraries***
***
if (MB_Private_UseMatchmaking && MMCommon::IsMatchServer()) {
	MMMatch::Load();
	VoteMap::Load();
	if (ServerAdmin != Null) ServerAdmin.SetLobbyInfo(False, AllPlayers.count, 0, 0.);
}
***

***Match_StartServer***
***
if (MB_Private_UseMatchmaking && MMCommon::IsMatchServer()) {
	Layers::Create("MM_Private_StartingMatch", MMMatch::GetMLStartingMatch());
	Layers::SetType("MM_Private_StartingMatch", CUILayer::EUILayerType::CutScene);
	Layers::Create("MM_Private_RematchVote", MMMatch::GetMLRematchVote());
	
	MMMatch::ForceAllowSubstitutes(True);
	
	// Initialize bots
	foreach (Player in AllPlayers) {
		declare Matchmaking_PlayerStatus for Player = MMMatch::PlayerStatus_Waiting();
		Matchmaking_PlayerStatus = MMMatch::PlayerStatus_Waiting();
	}
	
	MM_Private_CreateNewSession = True;
}
***

***Match_StartMap***
***
if (MB_Private_UseMatchmaking && MMCommon::IsMatchServer()) {
	MM_Private_MasterLogin = "";
	MM_Private_SessionClosed = False;
	
	if (MB_Settings_UseDefaultMatchmaking) {
		MM_Private_OpenSession(MM_Private_CreateNewSession);
	}
}
***

***Match_StartPlayLoop***
***
if (MB_Private_UseMatchmaking && MMCommon::IsMatchServer()) {
	if (MB_Settings_UseMatchmakingImplementationHelper && MM_Private_GetMatchId() == "") {
		Log::Error("[Matchmaking] You must open the match session. Use MM_OpenSession() at the beginning of ***Match_StartMap***.");
	}
}
***

***Match_BeforeUnloadMap***
***
if (MB_Private_UseMatchmaking && MMCommon::IsMatchServer()) {
	if (MB_Settings_UseDefaultMatchmaking) {
		MM_Private_CreateNewSession = MM_Private_CloseSession(MM_Private_MasterLogin);
	}
}
***

***Match_AfterUnloadMap***
***
if (MB_Private_UseMatchmaking && MMCommon::IsMatchServer()) {
	if (MB_Settings_UseMatchmakingImplementationHelper) {
		Log::Log("[Matchmaking] MM_Private_MasterLogin : "^MM_Private_MasterLogin^" | MB_Private_MatchIsRunning : "^MB_Private_MatchIsRunning());
		if (MM_Private_MasterLogin == "" && !MB_Private_MatchIsRunning()) {
			Log::Log("[Matchmaking] You did not provide a Master login. Use MM_SetMasterLogin() inside ***Match_EndMap***.");
		}
		if (!MM_Private_SessionClosed) {
			Log::Error("[Matchmaking] You must close the match session. Use MM_CloseSession() inside ***Match_BeforeUnloadMap***.");
		}
	}
}
***

***Match_EndServer***
***
if (MB_Private_UseMatchmaking && MMCommon::IsMatchServer()) {
	Layers::Destroy("MM_Private_StartingMatch");
	Layers::Destroy("MM_Private_RematchVote");
}
***

***Match_UnloadLibraries***
***
if (MB_Private_UseMatchmaking && MMCommon::IsMatchServer()) {
	MMMatch::Unload();
	VoteMap::Unload();
}
***

// ---------------------------------- //
// Functions
// ---------------------------------- //
// ---------------------------------- //
/// Announce the winner of the round
Void Lobby_Private_AnnounceWinner() {
	declare Winner <=> Scores::GetPlayerWinner();
	if (Winner != Null) {
		Message::SendBigMessage(
			MM_TL::Compose(_("$<%1$> is King of the Lobby!"), Winner.User.Name),
			5000,
			3
		);
	} else {
		Message::SendBigMessage(
			_("|Match|Draw"),
			5000,
			3
		);
	}
	UIManager.UIAll.SendNotice(
		"", 
		CUIConfig::ENoticeLevel::PlayerInfo, Null, 
		CUIConfig::EAvatarVariant::Default, 
		CUIConfig::EUISound::EndRound,
		0
	);
}

// ---------------------------------- //
/** Set start time
 *
 *	@param	_StartTime								The new start time
 */
Void MM_Private_SetStartTime(Integer _StartTime) {
	---MM_Private_SetStartTime---
}

// ---------------------------------- //
/** Get start time
 *
 *	@return														The start time
 */
Integer MM_Private_GetStartTime() {
	---MM_Private_GetStartTime---
}

// ---------------------------------- //
/** Set end time
 *
 *	@param	_EndTime									The new end time
 *	@param	_Soft											Use soft limit
 */
Void MM_Private_SetEndTime(Integer _EndTime, Boolean _Soft) {
	---MM_Private_SetEndTime---
}

// ---------------------------------- //
/** Get end time
 *
 *	@param	_Soft											Use soft limit
 *
 *	@return														The end time
 */
Integer MM_Private_GetEndTime(Boolean _Soft) {
	---MM_Private_GetEndTime---
}

// ---------------------------------- //
/** Create the rules manialink
 *
 *	@return		The manialink
 */
Text MM_Private_GetMatchRulesManialink() {
	declare ManialinkRules = "";
	---Lobby_Private_MatchRulesManialink---
	return MMLobby::GetMLRules(ManialinkRules);
}

// ---------------------------------- //
/** Set the maximum number of players in a clan
 *
 *	@param	_MaxPlayers		Players max number
 */
Void MM_Private_SetMaxPlayers(Integer _MaxPlayers) {
	MMCommon::MM_SetMaxPlayers(_MaxPlayers);
	MMLobby::MM_UpdateMaxPlayers(MMCommon::GetMaxPlayers());
}

// ---------------------------------- //
/** Set the new matchmaking format on this server
 *
 *	@param	_NewFormat		The new format
 */
Void MM_Private_SetFormat(Integer[] _NewFormat) {
	MMCommon::SetMatchFormat(_NewFormat);
	MMCommon::SetCurrentMatchFormat(_NewFormat);
	
	declare Max = 0;
	declare MatchFormat = MMCommon::GetMatchFormat();
	if (MMCommon::IsUniversalServer()) {
		foreach (PlayersNb in MatchFormat) {
			Max += PlayersNb;
		}
	} else {
		foreach (PlayersNb in MatchFormat) {
			if (PlayersNb > Max) Max = PlayersNb;
		}
	}
	
	MM_Private_SetMaxPlayers(Max);
	MMLobby::UpdateMatchFormat(MatchFormat);
	Layers::Update("MM_Private_StartingMatch", MMMatch::GetMLStartingMatch());
	
	if (MMCommon::IsMatchServer()) UseForcedClans = True;
}

// ---------------------------------- //
/** Set the progressive matchmaking formats on this server
 *
 *	@param	_AvailableFormats		List of available progressive formats
 */
Void MM_Private_SetProgressiveFormats(Integer[][] _ProgressiveFormats) {
	MMCommon::SetProgressiveMatchFormats(_ProgressiveFormats);
}

// ---------------------------------- //
/** Set the progressive matchmaking formats on this server
 *
 *	@param	_Format					The base format
 *	@param	_MinPlayersNb		Minimum number of players
 *	@param	_MaxPlayersNb		Maximum number of players
 */
Void MM_Private_SetProgressiveFormats(Integer[] _Format, Integer _MinPlayersNb, Integer _MaxPlayersNb) {
	declare Integer[][] Formats;
	for (I, _MinPlayersNb, _MaxPlayersNb) {
		declare Integer[] Format;
		for (J, 1, _Format.count) {
			Format.add(I);
		}
		Formats.add(Format);
	}
	MM_Private_SetProgressiveFormats(Formats);
}

// ---------------------------------- //
/** Check if a server is in match mode
 *
 *	@return		True if it's a match server, false otherwise
 */
Boolean MM_Private_IsMatchServer() {
	return MMCommon::IsMatchServer();
}

// ---------------------------------- //
/** Check if a server is in lobby mode
 *
 *	@return		True if it's a lobby server, false otherwise
 */
Boolean MM_Private_IsLobbyServer() {
	return MMCommon::IsLobbyServer();
}

// ---------------------------------- //
/** Check if we are in universal mode
 *
 *	@return		True if it's an universal server, false otherwise
 */
Boolean MM_Private_IsUniversalServer() {
	return MMCommon::IsUniversalServer();
}

// ---------------------------------- //
/** Check if we are on a matchmaking server
 *
 *	@return		True if it's a matchmaking server, false otherwise
 */
Boolean MM_Private_IsMatchmakingServer() {
	return MMCommon::IsMatchmakingServer();
}

// ---------------------------------- //
/** Check if the matchmaking is progressive
 *
 *	@return		True if the matchmaking is progressive, False otherwise
 */
Boolean MM_Private_MatchmakingIsProgressive() {
	return MMCommon::IsProgressiveMatchmaking();
}

// ---------------------------------- //
/** Get the match id
 *
 *	@return		The match id
 */
Text MM_Private_GetMatchId() {
	return MMMatch::GetMatchId();
}

// ---------------------------------- //
/** Set the scores to send to the api
 *
 *	@param	_Scores		The scores to send
 */
Void MM_Private_SetScores(Integer[] _Scores) {
	MMMatch::SetScores(_Scores);
}

// ---------------------------------- //
/// Send the match id to the ladder server to validate 100K matches
Void MM_Private_SetLadderMatchId() {
	declare MatchId = MMMatch::GetMatchIdInteger();
	if (MatchId < 0) return;
	
	Ladder_SetMatchMakingMatchId(MatchId);
}

// ---------------------------------- //
/** Check if a player is allowed to play by the matchmaking
 *
 *	@param	_Player		The player to check
 *
 *	@return			True if the player is allowed to play, false otherwise
 */
Boolean MM_Private_PlayerIsValid(CPlayer _Player) {
	return !MMCommon::IsMatchServer() || MMMatch::PlayerIsValid(_Player);
}

// ---------------------------------- //
/** Allow or not the mode to request substitutes
 *
 *	@param	_AllowSubstitutes		True to allow, false otherwise
 */
Void MM_Private_AllowSubstitutes(Boolean _AllowSubstitutes) {
	MMMatch::SetAllowSubstitutes(_AllowSubstitutes);
}

// ---------------------------------- //
/** Check if the substitutes are allowed
 *
 *	@return		True if the substitutes are allowed, false otherwise
 */
Boolean MM_Private_SubstitutesAreAllowed() {
	return MMMatch::GetAllowSubstitutes();
}

// ---------------------------------- //
/** Enable or disable the penalty for
 *	leaving a match
 *
 *	@param	_Enabled									True to enable the penalty
 *																		False to disable
 */
Void MM_Private_EnablePenalty(Boolean _Enabled) {
	MMMatch::EnablePenalty(_Enabled);
}

// ---------------------------------- //
/** Check if the penalty for leaving
 *	a match is enabled
 *
 *	@return														True if the penalty is enabled
 *																		False if the penalty is disabled
 */
Boolean MM_Private_PenaltyIsEnabled() {
	return MMMatch::PenaltyEnabled();
}

// ---------------------------------- //
/** Enable or disable the match server
 *	reconnect for the players
 *
 *	@param	_Enabled									True to enable the match reconnect
 *																		False to disable
 */
Void MM_Private_EnableMatchReconnect(Boolean _Enabled) {
	MMLobby::EnableMatchReconnect(_Enabled);
}

// ---------------------------------- //
/** Check if the match server reconnect
 *	is enabled
 *
 *	@return														True if the match reconnect is enabled
 *																		False if the match reconnect is disabled
 */
Boolean MM_Private_MatchReconnectIsEnabled() {
	return MMLobby::MatchReconnectEnabled();
}

// ---------------------------------- //
/** Get the slot selected by the matchmaking for the player
 *
 *	@param	_Player		The player to check
 *
 *	@return		The matchmaking slot of the player if found, -1 otherwise
 */
Integer MM_Private_GetRequestedSlot(CPlayer _Player) {
	return MMMatch::GetMatchPlayerSlot(_Player);
}

// ---------------------------------- //
/** Get the clan selected by the matchmaking for the player
 *	If the server is not in matchmaking mode, return the default requested clan
 *
 *	@param	_Player		The player to check
 *
 *	@return		The matchmaking clan of the player
 */
Integer MM_Private_GetRequestedClan(CPlayer _Player) {
	return MMMatch::GetMatchPlayerClan(_Player);
}

// ---------------------------------- //
/** Get the clan selected by the matchmaking for a login
 *
 *	@param	_Login		The login to check
 *
 *	@return		The matchmaking clan of the login
 */
Integer MM_Private_GetAssignedClan(Text _Login) {
	return MMMatch::GetPlayerClan(_Login);
}

// ---------------------------------- //
/** Vote to select the next map
 *
 *	@param	_ForceLoadMap		If True, load the selected map after the vote.
 *													If False, only modify the NextMapIndex variable.
 */
Void MM_Private_VoteForNextMap(Boolean _ForceLoadMap) {
	if (!S_MatchmakingVoteForMap) return;
	
	declare PrevHidden = UIManager.UIAll.OverlayHideCountdown;
	UIManager.UIAll.OverlayHideCountdown = False;
	
	VoteMap::Begin();
	{	// if (This is CTmMode)
		MM_Private_SetEndTime(VoteMap::GetVoteDuration(), True);
	}
	
	while (!VoteMap::CanStop()) {
		MB_Private_Yield();
		VoteMap::Loop();
		
		{ // if (This is CTmMode)
			if (MM_Private_GetEndTime(True) != VoteMap::GetVoteDuration()) {
				MM_Private_SetEndTime(VoteMap::GetVoteDuration(), True);
			}
		}
	}
	
	{ // if (This is CTmMode)
		MM_Private_SetEndTime(-1, True);
	}
	VoteMap::End();
	
	if (_ForceLoadMap) {
		declare NextMapInfo <=> VoteMap::GetNextMapInfo();
		if (NextMapInfo != Null && NextMapInfo.Id != Map.MapInfo.Id) {
			MB_Private_UnloadMap();
			MB_Private_LoadMap();
		}
	}
	
	UIManager.UIAll.OverlayHideCountdown = PrevHidden;
}

// ---------------------------------- //
/** Wait for all players to be ready on the server
 *
 *	@param	_MaxDuration	Maximum duration of the synchro (ms)
 */
Void MM_Private_WaitPlayers(Integer _MaxDuration) {
	declare PrevHidden = UIManager.UIAll.OverlayHideCountdown;
	UIManager.UIAll.OverlayHideCountdown = False;
	
	MM_Private_SetStartTime(Now);
	MM_Private_SetEndTime(-1, True);
	MMMatch::WaitPlayers_Begin(MM_Private_GetStartTime(), _MaxDuration);
	Layers::Attach("MM_Private_StartingMatch");
	
	while (MMMatch::WaitPlayers_IsRunning()) {
		MB_Private_Yield();
		
		declare WaitPlayersEndTime = MMMatch::WaitPlayers_Loop();
		if (WaitPlayersEndTime >= 0) {
			MM_Private_SetEndTime(WaitPlayersEndTime, True);
		}
	}
	
	MMMatch::WaitPlayers_End();
	Layers::Detach("MM_Private_StartingMatch");
	MM_Private_SetStartTime(-1);
	MM_Private_SetEndTime(-1, True);
	
	UIManager.UIAll.OverlayHideCountdown = PrevHidden;
}

// ---------------------------------- //
/// Prepare a new match
Void MM_Private_PrepareMatch() {
	declare PrevHidden = UIManager.UIAll.OverlayHideCountdown;
	UIManager.UIAll.OverlayHideCountdown = False;
	
	MM_Private_SetStartTime(Now);
	MM_Private_SetEndTime(MMMatch::PrepareMatch_Begin(MM_Private_GetStartTime(), S_MatchmakingWaitingTime), True);
	Layers::Attach("MM_Private_StartingMatch");
	
	while (MMMatch::PrepareMatch_IsRunning()) {
		MB_Private_Yield();
		
		MMMatch::PrepareMatch_Loop();
	}
	
	MMMatch::PrepareMatch_End();
	Layers::Detach("MM_Private_StartingMatch");
	MM_Private_SetStartTime(-1);
	MM_Private_SetEndTime(-1, True);
	
	UIManager.UIAll.OverlayHideCountdown = PrevHidden;
}

// ---------------------------------- //
/// Wait for a new match
Void MM_Private_MatchWait() {
	MMMatch::MM_MatchWait();
	
	// Waiting to get a match
	while (!MMMatch::CanStartMatch()) {
		MB_Private_Yield();
	}
	
	// Got a match, now prepare it
	MM_Private_PrepareMatch();
}

// ---------------------------------- //
/** Prepare the map for the matchmaking
 *
 *	@param	_NewSession								Start a new matchmaking session
 *																		or continue the previous one
 */
Void MM_Private_OpenSession(Boolean _NewSession) {
	if (_NewSession) {
		MM_Private_MatchWait();
		MM_Private_VoteForNextMap(True);
	} else {
		MM_Private_WaitPlayers(C_Match_Private_RematchWaitingTime);
	}
	MM_Private_SetLadderMatchId();
}

// ---------------------------------- //
/** Set the login of the match master
 *
 *	@param	_MasterLogin							Login of the master
 */
Void MM_Private_SetMasterLogin(Text _Login) {
	MM_Private_MasterLogin = _Login;
}

// ---------------------------------- //
/** Launch a vote to see if the players want a rematch
 *
 *	@return		True if the players want a rematch, False otherwise
 */
Boolean MM_Private_VoteForRematch() {
	if (S_MatchmakingRematchRatio < 0.) return False;
	
	Layers::Attach("MM_Private_RematchVote");
	Layers::Create("RematchVoteResults");
	Layers::Attach("RematchVoteResults");
	
	MMMatch::VoteForRematch_Begin(S_MatchmakingRematchRatio);
	
	MMMatch::AnimRematchVote("SlideIn");
	MB_Private_Sleep(300);
	
	while (MMMatch::VoteForRematch_IsRunning()) {
		MB_Private_Yield();
		
		declare LoginsList = MMMatch::VoteForRematch_Loop();
		if (LoginsList != "") Layers::Update("RematchVoteResults", LoginsList);
	}
	
	MMMatch::AnimRematchVote("SlideOut");
	MB_Private_Sleep(300);
	
	Layers::Detach("RematchVoteResults");
	Layers::Destroy("RematchVoteResults");
	Layers::Detach("MM_Private_RematchVote");
	
	declare Result = MMMatch::VoteForRematch_End();
	
	declare End = Now + 3000;
	while (Now < End) {
		MB_Private_Yield();
	}
	
	Message::CleanAllMessages();
	
	return Result;
}

// ---------------------------------- //
/** Send the result to the API
 *
 *	@param	_Master		The master of the match
 */
Void MM_Private_MatchEnd(Text _MasterLogin) {
	MMMatch::MatchEnd(_MasterLogin);
}

// ---------------------------------- //
/// End a match
Void MM_Private_MatchToLobby() {
	declare PrevHidden = UIManager.UIAll.OverlayHideCountdown;
	UIManager.UIAll.OverlayHideCountdown = False;
	
	MM_Private_SetStartTime(Now);
	MM_Private_SetEndTime(MMMatch::MatchToLobby_Begin(MM_Private_GetStartTime()), True);
	
	while (MMMatch::MatchToLobby_IsRunning()) {
		MB_Private_Yield();
		MMMatch::MatchToLobby_Loop();
	}
	
	MM_Private_SetStartTime(-1);
	MM_Private_SetEndTime(-1, True);
	
	MMMatch::MatchToLobby_End();
	
	UIManager.UIAll.OverlayHideCountdown = PrevHidden;
}

// ---------------------------------- //
/** Close the session before going to
 *	the next map.
 *
 *	@param	_MasterLogin							Login of the master
 *
 *	@return														True if the next session should be a new one
 *																		False if we can continue the current session
 */
Boolean MM_Private_CloseSession(Text _MasterLogin) {
	declare CreateNewSession = True;
	MM_Private_SessionClosed = True;
	
	// Force match end if the channel resquested a transfer of the players
	declare ForceMatchEnd = False;
	if (MMCommon::IsChannelServer() && MMCommon::GetChannelTransferUrl() != "") {
		MB_Private_StopMatch();
		ForceMatchEnd = True;
	}
	
	if ((UseClans && ClansNbPlayers[1] > 0 && ClansNbPlayers[2] > 0) || (!UseClans && Players.count > 0)) {
		if (MB_Private_MatchIsRunning()) {
			CreateNewSession = False;
			MM_Private_VoteForNextMap(False);
		} else {
			if (!ForceMatchEnd && MM_Private_RematchNb < S_MatchmakingRematchNbMax) {
				CreateNewSession = !MM_Private_VoteForRematch();
				MM_Private_RematchNb += 1;
			}
        
			if (CreateNewSession) {
				MM_Private_RematchNb = 0;
				MM_Private_MatchEnd(_MasterLogin);
				MM_Private_MatchToLobby();
			} else {
				MM_Private_VoteForNextMap(False);
			}
		}
	} else {
		MM_Private_RematchNb = 0;
		MM_Private_MatchEnd(_MasterLogin);
		MM_Private_MatchToLobby();
	}
	
	return CreateNewSession;
}