/**
 *	Laps mode
 */

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

#Const  CompatibleMapTypes  "Race"
#Const	Version		"2014-10-07"
#Const	ScriptName	"Laps.Script.txt"

#Include "TextLib" as TL
#Include "MathLib" as ML

// ---------------------------------- //
// Settings
// ---------------------------------- //
#Setting S_TimeLimit		0	as _("Time limit :")
#Setting S_ForceLapsNb		5	as _("Number of Laps :")
#Setting S_FinishTimeout	-1	as _("Finish timeout :")

// ---------------------------------- //
// Constants
// ---------------------------------- //
#Const C_NbBots 0
#Const Description _("""$fffIn $f00Laps$fff mode, the goal is to drive as far as possible by passing $f00checkpoints$fff.

The laps mode takes place on multilap (cyclical) maps, and is played in one go for every map.

When the time is up, the $f00winner$fff is the player who passed the most $f00checkpoints$fff. In case of draws, the winner is the player who passed the last checkpoint first.""")

// ---------------------------------- //
// Globales
// ---------------------------------- //

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

***InitServer***
***
declare Integer PrevTimeLimit;
declare Integer StartTime;
***

***StartServer***
***
// ---------------------------------- //
// Initialize mode
PrevTimeLimit = S_TimeLimit;
SetLapsNb(S_ForceLapsNb, StartTime);
StartTime = -1;

// ---------------------------------- //
// Initialize UI
UiLaps = True;
UI::LoadModules(["TimeGap", "Chrono", "CheckpointTime", "PrevBestTime", "SpeedAndDistance", "Countdown"]);
UI::DisplayTimeDiff(False);

// ---------------------------------- //
// Create scores table
ST2::SetStyle("LibST_TMBaseSolo");
ST2::SetModeIcon("Icons128x32_1|RT_Laps");
ST2::CreateCol("LibST_TMCheckpoints", "", "0", 3., 60.);
ST2::CreateCol("LibST_TMBestTime", "", "--:--.---", 8., 70.);
ST2::SetColTextAlign("LibST_TMBestTime", CMlControl::AlignHorizontal::Right);
ST2::SetColTextSize("LibST_TMCheckpoints", 1.5);
ST2::SetColTextSize("LibST_TMBestTime", 2.);
MB_SetScoresTableStyleFromXml(S_ScoresTableStylePath);
ST2::Build("TM");
***

***InitMap***
***
declare FirstFinish = True;
SetLapsNb(S_ForceLapsNb, StartTime);

// ---------------------------------- //
// Initialize scores
Scores_Clear();
ST2::ClearScores();
***

***StartMap***
***
// ---------------------------------- //
// Initialize map
MB_Ladder_OpenMatch_All();
Users_SetNbFakeUsers (C_NbBots, 0);

// ---------------------------------- //
// Warm up
declare ObjectiveNbLaps = Map.TMObjective_NbLaps;
if (ObjectiveNbLaps <= 0 || !Map.TMObjective_IsLapRace) ObjectiveNbLaps = 1;
declare MaxTime = (Map.TMObjective_AuthorTime / ObjectiveNbLaps) * S_WarmUpDuration;
declare WarmUpTimeLimit = Now + 3000 + MaxTime + (MaxTime / 6);
MB_WarmUp(WarmUpTimeLimit);

// ---------------------------------- //
// Initialize race
StartTime = Now + 3000;
SetTimeLimit(StartTime);
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;

// ---------------------------------- //
// Initialize scores
foreach (Score in Scores) {
	declare CanSpawn for Score = True;
	CanSpawn = True;
}

// ---------------------------------- //
// Spawn players for the race
foreach (Player in Players) {
	if (Player.Score == Null) continue;
	
	declare CanSpawn for Player.Score = True;
	TM2::StartRace(Player, StartTime);
	CanSpawn = False;
}
***

***WarmUp***
***
if (S_WarmUpDuration > 0) {		
	WarmUp::Begin();
	WarmUp::SetProgression(1, 1);
	CutOffTimeLimit = _TimeLimit;
	
	while (Now < CutOffTimeLimit && !WarmUp::Stop() && !ServerShutdownRequested && !MatchEndRequested) {
		MB_Yield();
		WarmUp::Loop();
		WarmUp::ManageEvents();
	}
	WarmUp::End();
	
	UIManager.UIAll.BigMessage = _("End of warmup, match starting...");
	UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
	CutOffTimeLimit = Now + 4000;
	while (Now < CutOffTimeLimit && !ServerShutdownRequested && !MatchEndRequested) {
		MB_Yield();
	}
	CutOffTimeLimit = -1;
	UIManager.UIAll.BigMessage = "";
	UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
}
***

***PlayLoop***
***
// ---------------------------------- //
// Update the map duration setting
if (PrevTimeLimit != S_TimeLimit) {
	PrevTimeLimit = S_TimeLimit;
	SetTimeLimit(StartTime);
}

// ---------------------------------- //
// Spawn players joining during the race
foreach (Player in Players) {
	if (Player.Score == Null) continue;
	
	declare CanSpawn for Player.Score = True;
	if (TM2::IsWaiting(Player) && CanSpawn) {
		TM2::StartRace(Player, StartTime);
		CanSpawn = False;
	}
}

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

// ---------------------------------- //
// End the map 
// If All players finished
if (Players.count > 0 && PlayersRacing.count <= 0) MB_StopMap = True;
// If time limit is reached
if (CutOffTimeLimit > 0 && Now >= CutOffTimeLimit) MB_StopMap = True;
***

***EndMap***
***
TM2::WaitRaceAll();
CutOffTimeLimit = -1;

// ---------------------------------- //
// Close ladder
Ladder_ComputeRank(CTmMode::ETmScoreSortOrder::BestRace_CheckpointsProgress);
MB_Ladder_CloseMatch();

Scores_Sort(CTmMode::ETmScoreSortOrder::BestRace_CheckpointsProgress);
if (Scores.existskey(0) &&  Scores[0].BestRace.Time > 0) {
	MB_VictoryMessage = TL::Compose(_("$<%1$> wins the map!"), Scores[0].User.Name);
}
***

// ---------------------------------- //
// Functions
// ---------------------------------- //
// ---------------------------------- //
/** Get the time left to the players to finish the map after the first player
 *
 *	@return 		The time left in ms
 */
Integer GetFinishTimeout() {
	declare FinishTimeout = 0;
	
	if (S_FinishTimeout >= 0) {
		FinishTimeout = S_FinishTimeout * 1000;
	} else {
		declare ObjectiveNbLaps = Map.TMObjective_NbLaps;
		if (ObjectiveNbLaps <= 0 || !Map.TMObjective_IsLapRace) ObjectiveNbLaps = 1;
		FinishTimeout = 5000 + (((Map.TMObjective_AuthorTime / ObjectiveNbLaps) * NbLaps) / 6);
	}
	
	return Now + FinishTimeout;
}

// ---------------------------------- //
/** Set the time limit
 *
 *	@param	_StartTime	The time at which the race started
 */
Void SetTimeLimit(Integer _StartTime) {
	// User define time limit with a setting
	if (S_TimeLimit > 0) {
		CutOffTimeLimit = _StartTime + (S_TimeLimit * 1000);
	} 
	// No time limit
	else if (S_TimeLimit == 0) {
		CutOffTimeLimit = -1;
	} 
	// Time limit auto adjusted
	else {
		declare ObjectiveNbLaps = Map.TMObjective_NbLaps;
		if (ObjectiveNbLaps <= 0) ObjectiveNbLaps = 1;
		declare TimePerLap = ML::NearestInteger((Map.TMObjective_BronzeTime + (Map.TMObjective_BronzeTime * 0.1)) / ObjectiveNbLaps);
		CutOffTimeLimit = _StartTime + (TimePerLap * NbLaps);
	}
}

// ---------------------------------- //
/** Set the number of laps 
 *
 *	@param _LapsNb	The number of laps
 *	@param _StartTime	The time at which the race started
 */
Void SetLapsNb(Integer _LapsNb, Integer _StartTime) {
	MB_SetLapsNb(_LapsNb);
	SetTimeLimit(_StartTime);
	
	ST2::SetFooterText(TL::Compose("%1 "^NbLaps, _("Number of Laps :")));
}