Initial commit of RoyalRounds

This commit is contained in:
Beu 2021-08-21 09:27:40 +02:00
parent ca2cf30ac1
commit ba599ef600

View File

@ -0,0 +1,718 @@
/**
* Royal Rounds mode
*/
#Extends "Libs/Nadeo/TMNext/TrackMania/Modes/TMNextBase.Script.txt"
#Const CompatibleMapTypes "TrackMania\\TM_Royal,TM_Royal"
#Const Version "2021-08-06"
#Const ScriptName "Modes/TrackMania/TM_RoyalRounds_Online.Script.txt"
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Libraries
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Include "TextLib" as TL
#Include "MathLib" as ML
#Include "Libs/Nadeo/CommonLibs/Common/Semver.Script.txt" as Semver
#Include "Libs/Nadeo/TMxSM/Race/PointsRepartition.Script.txt" as PointsRepartition
#Include "Libs/Nadeo/TMxSM/Race/Pause.Script.txt" as RacePause
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/TimeGap_Server.Script.txt" as UIModules_TimeGap
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenuOnline
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/SpectatorBase_Server.Script.txt" as UIModules_SpectatorBase
#Include "Libs/Nadeo/ModeLibs/Common/Utils.Script.txt" as ModeUtils
#Include "Libs/Nadeo/TMNext/TrackMania/Menu/Constants.Script.txt" as MenuConsts
#Include "Libs/Nadeo/TMNext/TrackMania/Modes/Rounds/StateManager.Script.txt" as StateMgr
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage
#Include "ManiaApps/Nadeo/TMxSM/Race/UIModules/Chrono_Server.Script.txt" as UIModules_Chrono
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Settings
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Setting S_PointsLimit 100 as _("Points limit")
#Setting S_FinishTimeout -1 as _("Finish timeout")
#Setting S_RoundsPerMap -1 as _("Number of rounds per map") ///< Number of round to play on one map before going to the next one
#Setting S_MapsPerMatch -1 as _("Number of maps per match") ///< Number of maps to play before finishing the match
#Setting S_UseTieBreak True as _("Use tie-break") ///< Continue to play the map until the tie is broken
#Setting S_SegmentsPerRounds 5 as "Number of segment to end the round"
#Setting S_RoundWaitingScreenDuration 20 as _("Round waiting screen duration") //< Maximum time spent waiting for players at the beginning of each round
#Setting S_PointsRepartition "" as _("Custom points distribution") //< comma separated points distribution. eg: "10,6,4,3,2,1"
/*#Setting S_WarmUpNb 0 as _("Number of warm up") // (Impossible at the moment https://forum.nadeo.com/viewtopic.php?f=51&p=8745#p8745)
#Setting S_WarmUpDuration 0 as _("Duration of one warm up")
#Setting S_WarmUpTimeout -1 as _("Warm up timeout")*/
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Constants
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
#Const C_ModeName "RoyalRounds"
//L16N [Rounds] Description of the mode rules
#Const Description _("$zIn $<$t$6F9RoyalRounds$z$z$> mode, the goal is to win a maximum number of $<$t$6F9points.\n\n$z$>The rounds mode consists of $<$t$6F9a series of races$z$>.\nWhen you finish a race in a good $<$t$6F9position$z$>, you get $<$t$6F9points$z$>, added to your total.\n\nThe $<$t$6F9winner$z$> is the first player whose total reaches the $<$t$6F9point limit$z$> (30 for example).")
#Const C_HudModulePath "" //< Path to the hud module
#Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/TMNext/TrackMania/Rounds/Rounds.Script.txt" //< Url of the mania app
#Const C_FakeUsersNb 0
#Const C_PointsRepartition [10, 6, 4, 3, 2, 1] ///< Default points repartition in rounds based modes. Can be overrided by S_PointsRepartition.
#Const C_Method_ForceEndRound "Trackmania.ForceEndRound"
#Const C_PointsLimit_NotReached 0
#Const C_PointsLimit_Reached 1
#Const C_PointsLimit_Tie 2
#Const C_UploadRecord True
#Const C_DisplayRecordGhost False
#Const C_DisplayRecordMedal False
#Const C_CelebrateRecordGhost True
#Const C_CelebrateRecordMedal True
#Const C_DisableSkipOutro True //< Prevent the players from pressing respawn/give up to cut the finish outro and respawn faster
declare Boolean Rounds_Settings_CanSpawnDefault;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Extends
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
***Match_LogVersions***
***
Log::RegisterScript(ScriptName, Version);
Log::RegisterScript(Semver::ScriptName, Semver::Version);
Log::RegisterScript(ModeUtils::ScriptName, ModeUtils::Version);
Log::RegisterScript(StateMgr::ScriptName, StateMgr::Version);
Log::RegisterScript(PointsRepartition::ScriptName, PointsRepartition::Version);
Log::RegisterScript(RacePause::ScriptName, RacePause::Version);
Log::RegisterScript(UIModules_Checkpoint::ScriptName, UIModules_Checkpoint::Version);
***
***Match_LoadLibraries***
***
XmlRpc::RegisterMethod(C_Method_ForceEndRound, """
* Name: {{{C_Method_ForceEndRound}}}
* Type: TriggerModeScriptEventArray
* Description: Stop the current round. Only available in Cup, Rounds and Team modes.
* Data:
- Version >=2.0.0:
```
[]
```
""");
StateMgr::Load();
PointsRepartition::Load();
RacePause::Load();
***
***Match_UnloadLibraries***
***
StateMgr::Unload();
RacePause::Unload();
PointsRepartition::Unload();
XmlRpc::UnregisterMethod(C_Method_ForceEndRound);
***
***Match_Settings***
***
MB_Settings_UseDefaultHud = (C_HudModulePath == "");
MB_Settings_UseDefaultPodiumSequence = False;
MB_Settings_UseDefaultIntroSequence = False;
MB_Settings_UseDefaultTimer = False;
Race_Settings_ResetPlayerRaceBetweenRounds = True;
Rounds_Settings_CanSpawnDefault = True;
***
***Match_Rules***
***
ModeInfo::SetName(C_ModeName);
ModeInfo::SetType(ModeInfo::C_Type_FreeForAll);
ModeInfo::SetRules(Description);
ModeInfo::SetStatusMessage("");
***
***Match_LoadHud***
***
if (C_HudModulePath != "") Hud_Load(C_HudModulePath);
***
***Match_AfterLoadHud***
***
UIModules_TimeGap::SetTimeGapMode(UIModules_TimeGap::C_TimeGapMode_CurRace);
UIModules_PauseMenuOnline::SetAllowPrevReplay(True);
ClientManiaAppUrl = C_ManiaAppUrl;
Race::SortScores(Race::C_Sort_TotalPoints);
UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_Points);
UIModules_Checkpoint::SetVisibilityTimeDiff(False);
UIModules_Checkpoint::SetRankMode(UIModules_Checkpoint::C_RankMode_CurrentRace);
UIModules_Checkpoint::SetAutoUISequenceFinish(False);
UIModules_PauseMenu_Online::SetHelp(Description);
// Hide SM Overlay
UIManager.UIAll.OverlayHideSpectatorControllers = True;
UIManager.UIAll.OverlayHideSpectatorInfos = True;
UIManager.UIAll.OverlayHideChrono = True;
UIManager.UIAll.OverlayHideCountdown = True;
***
***Match_Yield***
***
foreach (Event in XmlRpc.PendingEvents) {
if (Event.Type == CXmlRpcEvent::EType::CallbackArray) {
switch (Event.ParamArray1) {
case PointsRepartition::C_Method_SetPointsRepartition: {
declare Integer[] Rounds_PointsRepartitionBackUp for This;
Rounds_PointsRepartitionBackUp = PointsRepartition::GetPointsRepartition();
}
}
}
}
declare Rounds_PointsRepartitionSetting for This = S_PointsRepartition;
if (Rounds_PointsRepartitionSetting != S_PointsRepartition) {
Rounds_PointsRepartitionSetting = S_PointsRepartition;
declare PointsRepartition = C_PointsRepartition;
if (S_PointsRepartition != "") {
declare NewPointsRepartition = PointsRepartition::ConvertPointsRepartition(S_PointsRepartition);
if (NewPointsRepartition.count > 0) {
PointsRepartition = NewPointsRepartition;
}
}
PointsRepartition::SetPointsRepartition(PointsRepartition);
declare Integer[] Rounds_PointsRepartitionBackUp for This;
Rounds_PointsRepartitionBackUp = PointsRepartition::GetPointsRepartition();
}
foreach (Event in PendingEvents) {
switch (Event.Type) {
// Initialize players when they join the server
case CSmModeEvent::EType::OnPlayerAdded: {
StateMgr::InitializePlayer(Event.Player);
CarRank::InitializePlayer(Event.Player);
}
}
}
PointsRepartition::Yield();
StateMgr::Yield();
***
***Match_InitServer***
***
declare Integer Server_PointsLimit;
declare Integer Server_RoundsPerMap;
declare Integer Server_MapsPerMatch;
***
***Match_StartServer***
***
declare Integer[] Rounds_PointsRepartitionBackUp for This;
// Reload XmlRpc or load default values
if (Rounds_PointsRepartitionBackUp.count > 0) {
PointsRepartition::SetPointsRepartition(Rounds_PointsRepartitionBackUp);
} else {
declare PointsRepartition = C_PointsRepartition;
if (S_PointsRepartition != "") {
declare NewPointsRepartition = PointsRepartition::ConvertPointsRepartition(S_PointsRepartition);
if (NewPointsRepartition.count > 0) {
PointsRepartition = NewPointsRepartition;
}
}
PointsRepartition::SetPointsRepartition(PointsRepartition);
Rounds_PointsRepartitionBackUp = PointsRepartition::GetPointsRepartition();
}
// Enable the pause system
Pause::SetAvailability(True);
// Initialize mode
Clans::SetClansNb(0);
Scores::SaveInScore(Scores::C_Points_Match);
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
UsePvECollisions = True; //< Synchronize obstacles between all players
WarmUp::SetAvailability(False); // (Impossible at the moment https://forum.nadeo.com/viewtopic.php?f=51&p=8745#p8745)
Server_PointsLimit = S_PointsLimit - 1;
Server_RoundsPerMap = S_RoundsPerMap - 1;
Server_MapsPerMatch = S_MapsPerMatch - 1;
***
***Match_InitMap***
***
declare Integer Map_ValidRoundsNb;
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
declare CMapLandmark[] Map_Starts;
declare Integer Map_NextEmptyArmorCheckTime;
// Find start blocks
declare CMapLandmark[] Starts = Map::GetStarts();
declare CMapLandmark[Integer] SortedStarts;
foreach (Start in Starts) {
SortedStarts[Start.Order] = Start;
}
SortedStarts = SortedStarts.sortkey();
foreach (Start in SortedStarts) {
Map_Starts.add(Start);
}
if (Map_Starts.count > 0) {
Map::SetDefaultStart(Map_Starts[0]);
}
// We use `>= 0` and not `> 0` here because the waiting screen
// has two steps. First waiting for the presence of at least one player.
// Then waiting the desired amount of time. With `>= 0` we can have
// the first step without the second.
declare Integer WaitingScreenDuration = S_RoundWaitingScreenDuration;
if (WaitingScreenDuration >= 0) {
ModeUtils::PushAndApplyUISequence(UIManager.UIAll, CUIConfig::EUISequence::RollingBackgroundIntro);
// Wait for the connection of the first valid player to start the countdown
while (MB_MapIsRunning() && AllPlayers.count <= 0) {
MB_Sleep(1000);
}
// If all players are connected before the end of the countdown start immediatly
declare Integer WaitEndTime = Now + (WaitingScreenDuration * 1000);
while (WaitingScreenDuration > 0) {
if (WaitingScreenDuration > 3) {
UIModules_BigMessage::SetMessage("""The map starts in {{{WaitingScreenDuration}}} seconds""");
} else {
UIModules_BigMessage::SetMessage("");
UIManager.UIAll.BigMessage = """The map starts in {{{WaitingScreenDuration}}} seconds""";
}
WaitingScreenDuration = WaitingScreenDuration - 1;
MB_Sleep(1000);
}
UIManager.UIAll.BigMessage = "";
UIModules_BigMessage::SetMessage("");
ModeUtils::PopAndApplyUISequence(UIManager.UIAll);
}
***
***Match_StartMap***
***
// Add bot when necessary
Users_SetNbFakeUsers(C_FakeUsersNb, 0);
StartTime = Now + Race::C_SpawnDuration;
CarRank::Reset();
// Warm up (Impossible at the moment https://forum.nadeo.com/viewtopic.php?f=51&p=8745#p8745)
/*UIModules_ScoresTable::SetFooterInfo(_("Warm up"));
MB_WarmUp(S_WarmUpNb, S_WarmUpDuration * 1000, S_WarmUpTimeout * 1000);
*/
***
***Match_InitRound***
***
declare Boolean Round_ForceEndRound = False;
declare Boolean Round_SkipPauseRound = False; //< Skip the current round after the pause
declare Boolean Round_Skipped = True; //< Round skipped for another reason
***
***Match_StartRound***
***
// Initialize round
StartTime = Now + Race::C_SpawnDuration;
EndTime = -1;
Round_ForceEndRound = False;
Round_SkipPauseRound = False;
// Initialize scores
foreach (Score in Scores) {
if (Score.PrevRaceTimes.count > 0) {
Score_ClearPrevRace(Score);
}
Scores::SetPlayerRoundPoints(Score, 0);
}
// Setup pause
if (Pause::IsActive()) {
StartTime = Now;
+++Rounds_StartPause+++
while (RacePause::Loop(Pause::IsActive())) {
MB_Yield();
+++Rounds_PauseLoop+++
}
StartTime = -1;
+++Rounds_EndPause+++
Round_SkipPauseRound = True;
MB_StopRound();
}
+++Rounds_WaitForPlayers+++
MB_Yield(); //< Yield to wait for everyone to be ready
StartTime = Now + Race::C_SpawnDuration;
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
StateMgr::ForcePlayersStates([StateMgr::C_State_Playing]);
// Spawn players for the race
foreach (Player in Players) {
declare Integer CurrentSegment for Player.Score = 1;
CurrentSegment = 1;
Player.LandmarkOrderSelector_Race = CurrentSegment;
declare Boolean ModeRounds_CanSpawn for Player.Score = Rounds_Settings_CanSpawnDefault;
ModeRounds_CanSpawn = False;
Race::Start(Player, Map_Starts[0] , StartTime);
UIModules_Chrono::SetTimeOffset(Player, 0);
}
***
***Rounds_PlayerSpawned***
***
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_CurrentRace);
***
***Match_PlayLoop***
***
// Manage XmlRpc events
foreach (Event in XmlRpc.PendingEvents) {
if (Event.Type == CXmlRpcEvent::EType::CallbackArray) {
switch (Event.ParamArray1) {
case C_Method_ForceEndRound: {
Round_ForceEndRound = True;
}
}
}
}
// Pause activation
if (Pause::IsActive()) {
Round_ForceEndRound = True;
}
// If time limit is reached
if (EndTime > 0 && Now >= EndTime) {
MB_StopRound();
Round_Skipped = False;
}
// If forced end round or round skipped after pause
if (Round_ForceEndRound || Round_SkipPauseRound) {
MB_StopRound();
Round_Skipped = False;
}
// Manage race events
declare RacePendingEvents = Race::GetPendingEvents();
foreach (Event in RacePendingEvents) {
if (Event.Type == Events::C_Type_SkipOutro && C_DisableSkipOutro) {
Race::InvalidEvent(Event);
} else {
Race::ValidEvent(Event);
// Waypoint
if (Event.Type == Events::C_Type_Waypoint) {
CarRank::ThrottleUpdate(CarRank::C_SortCriteria_CurrentRace);
if (Event.Player != Null) {
if (Event.IsEndRace) {
declare Integer CurrentSegment for Event.Player.Score = 1;
if (CurrentSegment < S_SegmentsPerRounds) { // TODO Try to keep CP diff a the bottom of the screen
CurrentSegment = CurrentSegment + 1;
Race::StopSkipScoresTable(Event.Player);
declare Boolean ModeRounds_CanSpawn for Event.Player.Score = Rounds_Settings_CanSpawnDefault;
ModeRounds_CanSpawn = True;
} else {
Scores::UpdatePlayerBestRaceIfBetter(Event.Player);
Scores::UpdatePlayerBestLapIfBetter(Event.Player);
Scores::UpdatePlayerPrevRace(Event.Player);
ComputeLatestRaceScores();
Race::SortScores(Race::C_Sort_TotalPoints);
if (EndTime <= 0) {
EndTime = GetFinishTimeout(S_FinishTimeout);
}
}
}
}
} else if (Event.Type == Events::C_Type_GiveUp) {
if (Event.Player != Null) {
UIModules_SpectatorBase::SetCamModeAndFocus(Event.Player, UIModules_SpectatorBase::C_CamModes_Follow);
}
}
}
}
// Manage mode events
foreach (Event in PendingEvents) {
if (Event.HasBeenPassed || Event.HasBeenDiscarded) continue;
Events::Invalid(Event);
}
// Spawn players
if (PlayersNbDead > 0) { //< Check for unspawned players only if at least one player is unspawned
declare Boolean NoOneCanPlay = True;
foreach (Player in Players) {
declare Boolean ModeRounds_CanSpawn for Player.Score = Rounds_Settings_CanSpawnDefault;
if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned && ModeRounds_CanSpawn) {
NoOneCanPlay = False;
if (Race::IsReadyToStart(Player)) {
ModeRounds_CanSpawn = False;
declare Integer CurrentSegment for Player.Score = 1;
declare Integer Index;
if (CurrentSegment > Map_Starts.count) {
Index = Map_Starts.count - 1;
} else {
Index = CurrentSegment - 1;
}
Player.LandmarkOrderSelector_Race = Index + 1;
Race::Start(Player, Map_Starts[Index] , Now + Race::C_SpawnDuration);
UIModules_Chrono::SetTimeOffset(Player, Player.StartTime - StartTime);
}
}
}
if (NoOneCanPlay && PlayersNbAlive <= 0) MB_StopRound();
}
// Server info change
if (
Server_PointsLimit != S_PointsLimit ||
Server_RoundsPerMap != S_RoundsPerMap ||
Server_MapsPerMatch != S_MapsPerMatch
) {
Server_PointsLimit = S_PointsLimit;
Server_RoundsPerMap = S_RoundsPerMap;
Server_MapsPerMatch = S_MapsPerMatch;
UpdateScoresTableFooter(S_PointsLimit, S_RoundsPerMap, S_MapsPerMatch, Map_ValidRoundsNb);
}
***
***Match_EndRound***
***
Race::StopSkipOutroAll();
EndTime = -1;
StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]);
CarRank::Update(CarRank::C_SortCriteria_CurrentRace);
if (Semver::Compare(XmlRpc::GetApiVersion(), ">=", "2.1.1")) {
Scores::XmlRpc_SendScores(Scores::C_Section_PreEndRound, "");
}
if (Round_ForceEndRound || Round_SkipPauseRound || Round_Skipped) {
// Cancel points
foreach (Score in Scores) {
Scores::SetPlayerRoundPoints(Score, 0);
}
// Do not launch the forced end round sequence after a pause
if (!Round_SkipPauseRound) {
ForcedEndRoundSequence();
}
} else {
Map_ValidRoundsNb += 1;
// Get the last round points
ComputeLatestRaceScores();
Race::SortScores(Race::C_Sort_TotalPoints);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
MB_Sleep(3000);
// Add them to the total scores
ComputeScores();
Race::SortScores(Race::C_Sort_TotalPoints);
MB_Sleep(3000);
UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
if (MapIsOver(S_UseTieBreak, S_PointsLimit, Map_ValidRoundsNb, S_RoundsPerMap)) MB_StopMap();
}
***
***Match_EndMap***
***
if (MatchIsOver(S_UseTieBreak, S_PointsLimit, S_MapsPerMatch, S_RoundsPerMap)) MB_StopMatch();
if (!MB_MapIsRunning() && MB_MatchIsRunning()) MB_SkipPodiumSequence();
Race::SortScores(Race::C_Sort_TotalPoints);
Scores::SetPlayerWinner(Scores::GetBestPlayer(Scores::C_Sort_MatchPoints));
***
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Functions
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Update the scores table footer text
*
* @param _PointsLimit The points limit
* @param _RoundsPerMap The number of round per map
* @param _MapsPerMatch The number of maps per match
* @param _ValidRoundsNb Number of valid rounds played
*/
Void UpdateScoresTableFooter(Integer _PointsLimit, Integer _RoundsPerMap, Integer _MapsPerMatch, Integer _ValidRoundsNb) {
declare Text[] Parts;
declare Text Message = "";
if (_PointsLimit > 0) {
if (Parts.count > 0) Message ^= " | ";
Message ^= """%{{{Parts.count + 1}}}{{{_PointsLimit}}}""";
//L16N [Rounds] Number of points to reach to win the match.
Parts.add(_("Points limit : "));
}
if (_RoundsPerMap > 0) {
if (Parts.count > 0) Message ^= " | ";
Message ^= """%{{{Parts.count + 1}}}{{{ML::Min(_ValidRoundsNb+1, _RoundsPerMap)}}}/{{{_RoundsPerMap}}}""";
//L16N [Rounds] Number of rounds played during the map.
Parts.add(_("Rounds : "));
}
if (_MapsPerMatch > 0) {
if (Parts.count > 0) Message ^= " | ";
Message ^= """%{{{Parts.count + 1}}}{{{MB_GetMapCount()}}}/{{{_MapsPerMatch}}}""";
//L16N [Rounds] Number of maps played during the match.
Parts.add(_("Maps : "));
}
switch (Parts.count) {
case 0: UIModules_ScoresTable::SetFooterInfo(Message);
case 1: UIModules_ScoresTable::SetFooterInfo(TL::Compose(Message, Parts[0]));
case 2: UIModules_ScoresTable::SetFooterInfo(TL::Compose(Message, Parts[0], Parts[1]));
case 3: UIModules_ScoresTable::SetFooterInfo(TL::Compose(Message, Parts[0], Parts[1], Parts[2]));
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Get the time left to the players to finish the round after the first player
*
* @return The time left in ms
*/
Integer GetFinishTimeout(Integer _FinishTimeout) {
declare Integer FinishTimeout = 0;
if (_FinishTimeout >= 0) {
FinishTimeout = _FinishTimeout * 1000;
} else {
FinishTimeout = 5000;
if (Map.TMObjective_IsLapRace && Race::GetLapsNb() > 0 && Map.TMObjective_NbLaps > 0) {
FinishTimeout += ((Map.TMObjective_AuthorTime / Map.TMObjective_NbLaps) * Race::GetLapsNb()) / 6;
} else {
FinishTimeout += Map.TMObjective_AuthorTime / 6;
}
}
return Now + FinishTimeout;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Compute the latest race scores
Void ComputeLatestRaceScores() {
Race::SortScores(Race::C_Sort_PrevRaceTime);
// Points distributed between all players
declare Integer I = 0;
declare Integer[] PointsRepartition = PointsRepartition::GetPointsRepartition();
foreach (Score in Scores) {
if (Scores::GetPlayerPrevRaceTime(Score) > 0) {
declare Integer Points = 0;
if (PointsRepartition.count > 0) {
if (PointsRepartition.existskey(I)) {
Points = PointsRepartition[I];
} else {
Points = PointsRepartition[PointsRepartition.count - 1];
}
}
Scores::SetPlayerRoundPoints(Score, Points);
I += 1;
} else {
Scores::SetPlayerRoundPoints(Score, 0);
}
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/// Compute the map scores
Void ComputeScores() {
Scores::EndRound();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Check if the points limit was reached
*
* @param _UseTieBreak Prevent ties or not
* @param _PointsLimit Number of points to get to win the match
*
* @return C_PointsLimit_Reached if the points limit is reached
* C_PointsLimit_Tie if there is a tie
* C_PointsLimit_NotReached if the points limit is not reached
*/
Integer PointsLimitReached(Boolean _UseTieBreak, Integer _PointsLimit) {
declare Integer MaxScore = -1;
declare Boolean Tie = False;
foreach (Score in Scores) {
declare Integer Points = Scores::GetPlayerMatchPoints(Score);
if (Points > MaxScore) {
MaxScore = Points;
Tie = False;
} else if (Points == MaxScore) {
Tie = True;
}
}
if (_UseTieBreak && Tie) return C_PointsLimit_Tie; //< There is a tie and it is not allowed
if (_PointsLimit > 0 && MaxScore >= _PointsLimit) return C_PointsLimit_Reached; //< There is a points limit and it is reached
return C_PointsLimit_NotReached; //< There is no points limit or the points limit is not reached
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Check if we should go to the next map
*
* @param _UseTieBreak Prevent ties or not
* @param _PointsLimit Number of points to get to win the match
* @param _ValidRoundsNb Number of valid rounds played
* @param _RoundsPerMap Number of rounds to play to complete the map
*
* @return True if it is the case, false otherwise
*/
Boolean MapIsOver(Boolean _UseTieBreak, Integer _PointsLimit, Integer _ValidRoundsNb, Integer _RoundsPerMap) {
if (PointsLimitReached(_UseTieBreak, _PointsLimit) == C_PointsLimit_Reached) return True; //< There is a points limit and it is reached
if (_RoundsPerMap > 0 && _ValidRoundsNb >= _RoundsPerMap) return True; //< There is a rounds limit and it is reached
return False;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
/** Check if we should go to the next match
*
* @param _UseTieBreak Prevent ties or not
* @param _PointsLimit Number of points to get to win the match
* @param _MapsPerMatch Number of maps to play to complete a match
* @param _RoundsPerMap Number of rounds to play to complete the map
*
* @return True if it is the case, false otherwise
*/
Boolean MatchIsOver(Boolean _UseTieBreak, Integer _PointsLimit, Integer _MapsPerMatch, Integer _RoundsPerMap) {
declare Integer PointsLimitReached = PointsLimitReached(_UseTieBreak, _PointsLimit);
if (_MapsPerMatch > 1 && PointsLimitReached == C_PointsLimit_Tie) return False; //< Ties are allowed if the map was skipped and match is played on one map only
if (PointsLimitReached == C_PointsLimit_Reached) return True; //< There is a points limit and it is reached
if (_MapsPerMatch > 1 && MB_GetMapCount() >= _MapsPerMatch) return True; //< There is a maps limit and it is reached
if (_MapsPerMatch <= 1 && _RoundsPerMap <= 0) return True;
return False;
}
// Display a message if the round was skipped
Void ForcedEndRoundSequence() {
declare PrevUISequence = UIManager.UIAll.UISequence;
declare PrevBigMessage = UIManager.UIAll.BigMessage;
declare PrevBigMessageSound = UIManager.UIAll.BigMessageSound;
declare PrevBigMessageSoundVariant = UIManager.UIAll.BigMessageSoundVariant;
UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
UIManager.UIAll.BigMessage = _("Round skipped");
UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::EndRound;
UIManager.UIAll.BigMessageSoundVariant = 0;
MB_Sleep(3000);
UIManager.UIAll.BigMessageSoundVariant = PrevBigMessageSoundVariant;
UIManager.UIAll.BigMessageSound = PrevBigMessageSound;
UIManager.UIAll.BigMessage = PrevBigMessage;
UIManager.UIAll.UISequence = PrevUISequence;
}