/** * 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; }