/**
 *	Cup mode
 */

#Extends "Modes/TrackMania/RoundsBase.Script.txt"

#Const	CompatibleMapTypes	"Race"
#Const	Version				"2015-02-06"
#Const	ScriptName			"Cup.Script.txt"

#Include "TextLib" as TL

// ---------------------------------- //
// Settings
// ---------------------------------- //
#Setting S_RoundsPerMap		5	as _("Rounds per map :")
#Setting S_NbOfWinners		3	as _("Number of winners :")
#Setting S_NbOfPlayersMax	4	as _("Maximum number of players in matchmaking :")
#Setting S_NbOfPlayersMin	4	as _("Minimum number of players in matchmaking :")
#Setting S_WarmUpDuration	2	///< Overload from ModeBase

// ---------------------------------- //
// Constants
// ---------------------------------- //
#Const C_BotsNb 0
#Const Description _("""$fffThe cup mode consists of $f00a series of races on multiple maps$fff.

When you finish a race in a good $f00position$fff, you get $f00points$fff added to your total.
Servers might propose warmup races to get familiar with a map first.

To win, you must first reach the $f00point limit$fff to become a $f00finalist$fff. Once you are a finalist, you must finish a race in $f00first position$fff to win the cup.The cup mode ends once 3 players have managed to become finalists and to finish first.""")

// ---------------------------------- //
// Globales
// ---------------------------------- //
declare Integer G_NbOfValidRounds;

// ---------------------------------- //
// Extend
// ---------------------------------- //
***LogVersion***
***
MB_LogVersion(ScriptName, Version);
***

***LobbyStartServer***
***
MM_SetFormat(GetMatchmakingFormat(S_NbOfPlayersMax));
if (S_NbOfPlayersMax > S_NbOfPlayersMin) {
	declare Formats = Integer[][];
	for (I, S_NbOfPlayersMin, S_NbOfPlayersMax-1) {
		if (I > 0) Formats.add(GetMatchmakingFormat(I));
	}
	MM_SetProgressiveFormats(Formats);
}
***

***ManialinkRules***
***
ManialinkRules = """<label posn="-62.5 25" sizen="125 50" autonewline="1" maxline="10" textemboss="1" textsize="1.5" text="{{{Description}}}" />""";
***

***InitMatchmaking***
***
// ---------------------------------- //
// Matchmaking mode
if (MM_IsMatchServer()) {
	MM_Init(GetMatchmakingFormat(S_NbOfPlayersMax));
}
***

***InitServer***
***
declare PrevPointsLimit = S_PointsLimit;
declare IsSameMatch = False;
***

***StartServer***
***
ST2::SetStyle("LibST_TMBaseSolo");
ST2::SetModeIcon("Icons128x32_1|RT_Cup");
ST2::SetFormat(1, 4);
ST2::CreateCol("LibST_TMPrevRaceDeltaPoints", "", "0", 2., 60.);
ST2::CreateCol("LibST_TMPoints", "", "0", 6., 70.);
ST2::SetColTextAlign("LibST_TMPrevRaceDeltaPoints", CMlControl::AlignHorizontal::Right);
ST2::SetColTextAlign("LibST_TMPoints", CMlControl::AlignHorizontal::Right);
ST2::SetColWidth("LibST_ManiaStars", 1.7);
ST2::SetColWidth("LibST_Tools", 1.5);
ST2::SetColTextSize("LibST_Name", 3.);
ST2::SetColTextSize("LibST_TMPoints", 4.);
MB_SetScoresTableStyleFromXml(S_ScoresTableStylePath);
ST2::Build("TM");
***

***StartMatch***
***
foreach (User in Users) {
	declare IsWinner for User = -1;
	IsWinner = -1;
}
Scores_Clear();
ST2::ClearScores();
***

***InitMap***
***
// Skip intro in matchmaking
if (MM_IsMatchServer()) MB_UseIntro = False;
else MB_UseIntro = True;
***

***StartMap***
***
// ---------------------------------- //
// Initialize map
Users_SetNbFakeUsers(C_BotsNb, 0);
SetUiScoresPointsLimit(S_PointsLimit);
G_NbOfValidRounds = 0;

ST2::SetFooterText(TL::Compose("%1 "^S_PointsLimit, _("Points limit : ")));

// ---------------------------------- //
// Matchmaking
if (MM_IsMatchServer()) {
	// ---------------------------------- //
	// Set matchmaking scores
	MM_SetScores([GetBestScore()]);
	
	// ---------------------------------- //
	// Wait players when using matchmaking
	if (!IsRematch && !IsSameMatch) {
		MM_MatchWait();
		MM_VoteForNextMap(True);
	} else {
		MM_WaitPlayers(15000);
	}
	
	IsRematch = False;
	MM_AllowSubstitutes(False);
} 
// ---------------------------------- //
// Warm up
else {
	declare WarmUpTimeLimit = -1;
	if (S_UseAlternateRules) {
		G_RoundStartTime = Now + 3000;
		WarmUpTimeLimit = (GetFinishTimeout() - Now) / 1000;
	}
	MB_WarmUp(WarmUpTimeLimit);
}
***

***CanSpawn***
***
foreach (Score in Scores) {
	declare CanSpawn for Score = True;
	
	if (Score.Points > S_PointsLimit) {
		CanSpawn = False;
	} else if (MM_IsMatchServer()) {
		declare Player <=> TM2::GetPlayer(Score.User.Login);
		CanSpawn = MM_PlayerIsValid(Player);
	} else {
		CanSpawn = True;
	}
}
***

***PlayLoop***
***
// ---------------------------------- //
// Manage events
foreach (Event in PendingEvents) {
	PassOn(Event);
	XmlRpc::PassOn(Event);
	
	// ---------------------------------- //
	// Waypoint
	if (Event.Type == CTmModeEvent::EType::WayPoint) {
		if (Event.IsEndRace) {
			if (Event.Player.Score != Null) {
				if (Event.Player.Score.BestRace.Compare(Event.Player.CurRace, CTmResult::ETmRaceResultCriteria::Time) <= 0) {
					Event.Player.Score.BestRace = Event.Player.CurRace;
				}
			}
			Event.Player.Score.PrevRace = Event.Player.CurRace;
			ComputeLatestRaceScores();
			Scores_Sort(CTmMode::ETmScoreSortOrder::TotalPoints);
			TM2::EndRace(Event.Player);
			
			// ---------------------------------- //
			// Start the countdown if it's the first player to finish
			if (CutOffTimeLimit <= 0) {
				CutOffTimeLimit = GetFinishTimeout();
			}
		}
		if (Event.IsEndLap) {
			if (Event.Player.Score != Null) {
				if (Event.Player.Score.BestLap.Compare(Event.Player.CurLap, CTmResult::ETmRaceResultCriteria::Time) <= 0) {
					Event.Player.Score.BestLap = Event.Player.CurLap;
				}
			}
		}
	}
	// ---------------------------------- //
	// GiveUp
	else if (Event.Type == CTmModeEvent::EType::GiveUp) {
		TM2::WaitRace(Event.Player);
	}
}

// ---------------------------------- //
// Server info change
if (PrevPointsLimit != S_PointsLimit) {
	PrevPointsLimit = S_PointsLimit;
	
	SetUiScoresPointsLimit(S_PointsLimit);
	ST2::SetFooterText(TL::Compose("%1 "^S_PointsLimit, _("Points limit : ")));
}
***

***EndRound***
***
TM2::WaitRaceAll();
CutOffTimeLimit = -1;
SetUiScoresPointsLimit(S_PointsLimit);

if (ForceEndRound) {
	ForcedEndRoundSequence();
} else {
	// Get the last round points
	ComputeLatestRaceScores();
	Scores_Sort(CTmMode::ETmScoreSortOrder::TotalPoints);
	UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
	UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
	MB_Sleep(3000);
	// Add them to the total scores
	ComputeScores();
	Scores_Sort(CTmMode::ETmScoreSortOrder::TotalPoints);
	MB_Sleep(3000);
	UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
	UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
	UIManager.UIAll.BigMessage = "";
	
	// ---------------------------------- //
	// Match is over, we have all the winners
	if (MatchIsOver()) {
		MB_StopMatch = True;
		IsSameMatch = False;
	}
	// ---------------------------------- //
	// Map is over, we played all the rounds
	else if (MapIsOver()) {
		MB_StopMap = True;
		IsSameMatch = True;
	}
}

// ---------------------------------- //
// Set matchmaking scores
if (MM_IsMatchServer()) {
	declare BestScore = GetBestScore();
	MM_SetScores([BestScore]);
}
***

***EndMap***
***
UiScoresPointsLimit = -1;
MB_UsePodium = False;

if (MB_StopMatch) {
	// ---------------------------------- //
	// Close ladder
	if (MM_IsMatchServer() && (Players.count == 0 || MM_RestartMatchmaking) && MB_SectionRoundNb <= 1) {
		MM_SetLadderMatchId();
		MB_Ladder_CancelMatch();
		MB_Log("Cancel match and don't give LP because one of the team left the match.");
	} else {
		if (MM_IsMatchServer()) MM_SetLadderMatchId();
		Ladder_ComputeRank(CTmMode::ETmScoreSortOrder::TotalPoints);
		MB_Ladder_CloseMatch();
	}

	Scores_Sort(CTmMode::ETmScoreSortOrder::TotalPoints);
	if (Scores.existskey(0) &&  Scores[0].Points > 0) {
		if (MM_IsMatchServer()) MasterLogin = Scores[0].User.Login;
		MB_VictoryMessage = TL::Compose(_("$<%1$> wins the match!"), Scores[0].User.Name);
	}
	MB_UsePodium = True;
}
***

// ---------------------------------- //
// Functions
// ---------------------------------- //
// ---------------------------------- //
/** Get matchmaking format
 *
 *	@param	_PlayersNb		The number of players
 *
 *	@return					The format with the given number of players
 */
Integer[] GetMatchmakingFormat(Integer _PlayersNb) {
	declare Format = Integer[];
	for (I, 1, _PlayersNb) {
		Format.add(1);
	}
	return Format;
}

// ---------------------------------- //
/**	Set the cup points limit
 *
 *	@param	_PointsLimit	The new points limit
 */
Void SetUiScoresPointsLimit(Integer _PointsLimit) {
	UiScoresPointsLimit = _PointsLimit;
	ST2::SetUiScoresPointsLimit(_PointsLimit);
}

// ---------------------------------- //
/** Get the time left to the players to finish the round after the first player
 *
 *	@return 				The time left in ms
 */
Integer GetFinishTimeout() {
	declare FinishTimeout = 0;
	
	if (S_FinishTimeout >= 0) {
		FinishTimeout = S_FinishTimeout * 1000;
	} else {
		FinishTimeout = 5000;
		if (Map.TMObjective_IsLapRace && NbLaps > 0 && Map.TMObjective_NbLaps > 0) {
			FinishTimeout += ((Map.TMObjective_AuthorTime / Map.TMObjective_NbLaps) * NbLaps) / 6;
		} else {
			FinishTimeout += Map.TMObjective_AuthorTime / 6;
		}
	}
	
if (S_UseAlternateRules) {
		if (Map.TMObjective_IsLapRace && NbLaps > 0 && Map.TMObjective_NbLaps > 0) {
			return G_RoundStartTime + ((Map.TMObjective_AuthorTime / Map.TMObjective_NbLaps) * NbLaps) + FinishTimeout;
		} else {
			return G_RoundStartTime + Map.TMObjective_AuthorTime + FinishTimeout;
		}
	} else {
		return Now + FinishTimeout;
	}
	
	// Default value from TMO, TMS (not used)
	return Now + 15000;
}

// ---------------------------------- //
/** Announce a new winner in the chat
 *
 *	@param	_Name			The name of the new winner
 *	@param	_Rank			The rank of the new winner
 */
Void AnnounceWinner(Text _Name, Integer _Rank) {
	declare Message = "";
	switch (_Rank) {
		case 1: Message = TL::Compose("$<%1$> takes 1st place!", _Name);
		case 2: Message = TL::Compose("$<%1$> takes 2nd place!", _Name);
		case 3: Message = TL::Compose("$<%1$> takes 3rd place!", _Name);
		default: Message = TL::Compose("$<%1$> takes %2th place!", _Name, TL::ToText(_Rank));
	}
	
	UIManager.UIAll.SendChat(Message);
	UIManager.UIAll.BigMessage = Message;
}

// ---------------------------------- //
/// Compute the latest race scores
Void ComputeLatestRaceScores() {
	Scores_Sort(CTmMode::ETmScoreSortOrder::PrevRace_Time);
	
	// Only points for the first players
	if (S_UseAlternateRules) {
		declare Points = 1;
		
		foreach (Score in Scores) {
			if (Score.PrevRace.Time > 0) {
				Score.PrevRaceDeltaPoints = Points;
				if (Points > 0) Points -= 1;
			} else {
				Score.PrevRaceDeltaPoints = 0;
			}
		}
	} 
	// Points distributed between all players
	else {		
		declare I = 0;
		declare J = 0;
		foreach (Score in Scores) {
			if (Score.PrevRace.Time > 0) {
				declare Points = 0;
				if (MB_PointsRepartition.count > 0) {
					if (MB_PointsRepartition.existskey(I)) {
						Points = MB_PointsRepartition[I];
					} else {
						Points = MB_PointsRepartition[MB_PointsRepartition.count - 1];
					}
				}
				
				// If the player is finalist but didn't take the first place, he doesn't earn points
				// or if he already won
				if ((Score.Points == S_PointsLimit && J > 0) || Score.Points > S_PointsLimit) {
					Score.PrevRaceDeltaPoints = 0;
				} else {
					Score.PrevRaceDeltaPoints = Points;
				}
				I += 1;
			} else {
				Score.PrevRaceDeltaPoints = 0;
			}
			J += 1;
		}
	}
}

// ---------------------------------- //
/// Compute the map scores
Void ComputeScores() {
	declare RoundIsValid = False;
	declare NbOfWinners = 0;
	declare NewWinner = False;
	
	Scores_Sort(CTmMode::ETmScoreSortOrder::TotalPoints);
	
	foreach (Score in Scores) {
		if (Score.PrevRaceDeltaPoints > 0) RoundIsValid = True;
		
		// Already won
		if (Score.Points > S_PointsLimit) {
			Score.Points = S_PointsLimit + 1 + S_NbOfWinners - NbOfWinners;
			NbOfWinners += 1;
		} 
		// New winner
		else if (Score.Points == S_PointsLimit && Score.PrevRaceDeltaPoints > 0 && !NewWinner) {
			Score.Points = S_PointsLimit + 1 + S_NbOfWinners - NbOfWinners;
			NbOfWinners += 1;
			NewWinner = True;
			AnnounceWinner(Score.User.Name, NbOfWinners);
		} 
		// Standard round finish
		else {
			Score.Points += Score.PrevRaceDeltaPoints;
			if (Score.Points > S_PointsLimit) Score.Points = S_PointsLimit;
		}
		
		Score.PrevRaceDeltaPoints = 0;
	}
	
	if (RoundIsValid) G_NbOfValidRounds += 1;
}

// ---------------------------------- //
/** Get the best score
 *
 *	@return					The best score
 */
Integer GetBestScore() {
	declare Max = 0;
	foreach (Score in Scores) {
		if (Score.Points > Max) Max = Score.Points;
	}
	return Max;
}

// ---------------------------------- //
/** Check if we should go to the next map
 *
 *	@return					True if the map is over, false otherwise
 */
Boolean MapIsOver() {
	if (G_NbOfValidRounds >= S_RoundsPerMap) return True;
	return False;
}

// ---------------------------------- //
/** Check if we have found all the winners
 *
 *	@return					True if the match is over, false otherwise
 */
Boolean MatchIsOver() {
	declare NbOfWinners = 0;
	foreach (Score in Scores) {
		if (Score.Points > S_PointsLimit) NbOfWinners += 1;
	}
	
	declare WinnersLimit = Players.count - 1;
	if (WinnersLimit < 1) WinnersLimit = 1;
	if (NbOfWinners >= S_NbOfWinners || NbOfWinners >= WinnersLimit) return True;
	
	return False;
}