/** * Seeding Time Attack mode */ #Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaBase.Script.txt" #Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race" #Const Version "2023-09-25" #Const ScriptName "Modes/TM2020-Gamemodes/TM_SeedingTimeAttack_Online.Script.txt" // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Libraries // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // #Include "TextLib" as TL #Include "MathLib" as ML #Include "Libs/Nadeo/CMGame/Utils/Task.Script.txt" as Task #Include "Libs/Nadeo/Trackmania/Modes/TimeAttack/StateManager.Script.txt" as StateMgr #Include "Libs/Nadeo/Trackmania/Modes/TrophyRanking.Script.txt" as TrophyRanking #Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts #Include "Libs/Nadeo/CMGame/Modes/Utils.Script.txt" as ModeUtils #Include "Libs/Nadeo/CMGame/Utils/Semver.Script.txt" as Semver // UI from Race #Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/TimeGap_Server.Script.txt" as UIModules_TimeGap #Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage #Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/Checkpoint_Server.Script.txt" as UIModules_Checkpoint #Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/ScoresTable_Server.Script.txt" as UIModules_ScoresTable #Include "Libs/Nadeo/TMGame/Modes/Base/UIModules/PauseMenuOnline_Server.Script.txt" as UIModules_PauseMenu_Online #Include "Libs/Nadeo/Trackmania/Modes/TimeAttack/UIModules/EndMatchTrophy_Server.Script.txt" as UIModules_EndMatchTrophy // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Settings // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // #Setting S_UseTheWorstTimeForDNF True as "" #Setting S_MalusTimeForDNF 10000 as "Time to add (or substract) in ms for who DNF" #Setting S_MapsPerMatch 3 as _("Number of maps per match") ///< Number of maps to play before finishing the match #Setting S_TimeLimit 300 as _("Time limit") ///< Time limit before going to the next map #Setting S_WarmUpNb 0 as _("Number of warm up") #Setting S_WarmUpDuration 0 as _("Duration of one warm up") #Setting S_WarmUpTimeout -1 as _("Warm up timeout") #Setting S_ForceLapsNb 0 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Constants // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // #Const C_ModeName "Time Attack" //L16N [Time Attack] Description of the mode rules #Const Description _("$zIn $<$t$6F9Time Attack$> mode, the goal is to set the $<$t$6F9best time$>.\n\nYou have as many tries as you want, and you can $<$t$6F9retry$> when you want by pressing the respawn key.\n\nWhen the time is up, the $<$t$6F9winner$> is the player with the $<$t$6F9best time$>.") #Const C_HudModulePath "" //< Path to the hud module #Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/TimeAttack.Script.txt" //< Url of the mania app #Const C_FakeUsersNb 0 #Const C_UploadRecord True #Const C_DisplayRecordGhost True #Const C_DisplayRecordMedal True #Const C_CelebrateRecordGhost True #Const C_CelebrateRecordMedal True #Const C_DisplayWorldTop True #Const C_TrophyTaskTimeout 5000 #Const C_TrophyAnimationDuration 4000 #Const C_TrophyDisplayDuration 7000 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Extends // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ***Match_LogVersions*** *** Log::RegisterScript(ScriptName, Version); Log::RegisterScript(StateMgr::ScriptName, StateMgr::Version); *** ***Match_LoadLibraries*** *** StateMgr::Load(); *** ***Match_UnloadLibraries*** *** StateMgr::Unload(); *** ***Match_Settings*** *** MB_Settings_UseDefaultTimer = False; MB_Settings_UseDefaultHud = (C_HudModulePath == ""); MB_Settings_UseDefaultPodiumSequence = False; *** ***Match_Rules*** *** ModeInfo::SetName(C_ModeName); ModeInfo::SetType(ModeInfo::C_Type_FreeForAll); ModeInfo::SetRules(Description); ModeInfo::SetStatusMessage(_("TYPE: Free for all\nOBJECTIVE: Set the best time on the track.")); *** ***Match_LoadHud*** *** if (C_HudModulePath != "") Hud_Load(C_HudModulePath); *** ***Match_AfterLoadHud*** *** UIManager.UIAll.ScoreTableOnlyManialink = True; UIModules_Checkpoint::SetRankMode(UIModules_Checkpoint::C_RankMode_BestRace); ClientManiaAppUrl = C_ManiaAppUrl; Race::SortScores(Race::C_Sort_BestRaceTime); UIModules_TimeGap::SetTimeGapMode(UIModules_TimeGap::C_TimeGapMode_BestRace); UIModules_PauseMenu_Online::SetHelp(Description); UIManager.UIAll.OverlayHideCountdown = True; UIManager.UIAll.OverlayHideSpectatorInfos = True; UIManager.UIAll.OverlayHideChrono = True; SetML(); *** ***Match_Yield*** *** 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); if (Event.Player != Null) { Log::Log("[Match_Yield][PendingEvents] New player added: " ^ Event.Player.User.Name); declare Boolean Match_CanForceTrophyRankUpdate for This; TrophyRanking::InitializeUser(Event.Player.User, Match_CanForceTrophyRankUpdate); if (Scores::GetPlayerMatchPoints(Event.Player.Score) == 0) { declare Integer CumulateWorstTimeOfPreviousMaps for This; Log::Log("[Match_Yield][PendingEvents] New Player with : " ^ CumulateWorstTimeOfPreviousMaps ^ " points"); Scores::SetPlayerMatchPoints(Event.Player.Score, CumulateWorstTimeOfPreviousMaps); } } } } } StateMgr::Yield(); TrophyRanking::Yield(); *** ***Match_StartServer*** *** // Initialize mode Clans::SetClansNb(0); GiveUpBehaviour_RespawnAfter = True; CrudeExtrapolation_AllowDelay = True; Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_GiveUpBeforeFirstCheckpoint); StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]); WarmUp::SetAvailability(True); Race::SetupRecord( MenuConsts::C_ScopeType_Season, MenuConsts::C_ScopeType_PersonalBest, MenuConsts::C_GameMode_TimeAttack, "", C_UploadRecord, C_DisplayRecordGhost, C_DisplayRecordMedal, C_CelebrateRecordGhost, C_CelebrateRecordMedal, C_DisplayWorldTop ); CarRank::Reset(); Scores::SaveInScore(Scores::C_Points_Match); UIModules_Record::SetSpecialVisibility(False); *** ***Match_InitMatch*** *** declare Task::K_Task Match_TrophyTask; declare Integer Match_TrophyTaskEndTime; declare Integer Match_MatchDuration; declare Boolean Match_CanForceTrophyRankUpdate for This = False; declare netwrite Text Net_ScriptEnvironment for Teams[0] = S_ScriptEnvironment; declare netwrite Integer[Text] Net_MatchPoints for Teams[0]; Net_MatchPoints = []; declare netwrite Integer[Text] Net_RoundPoints for Teams[0]; Net_RoundPoints = []; declare Integer CumulateWorstTimeOfPreviousMaps for This; CumulateWorstTimeOfPreviousMaps = 0; declare Integer WorstTime for This; WorstTime = 0; *** ***Match_AfterLoadMap*** *** Match_CanForceTrophyRankUpdate = True; *** ***Match_InitMap*** *** declare Integer Map_TimeLimit; declare Integer Map_MapStartTime; declare Integer Map_MalusTimeForDNF; declare Integer Map_MapsPerMatch; declare Boolean Map_UseTheWorstTimeForDNF; *** ***Match_StartMap*** *** // Add bot when necessary if (S_ScriptEnvironment == "production") Users_SetNbFakeUsers(C_FakeUsersNb, 0); // Warm up UIModules_ScoresTable::SetFooterInfo(_("Warmup")); GiveUpBehaviour_RespawnAfter = False; MB_WarmUp(S_WarmUpNb, S_WarmUpDuration * 1000, S_WarmUpTimeout * 1000); GiveUpBehaviour_RespawnAfter = True; // Initialize race SetScoresTableScoreMode(Race::IsIndependentLaps(), False); StartTime = Now + Race::C_SpawnDuration; // Spawn players for the race foreach (Player in Players) { Race::Start(Player, StartTime); } StateMgr::ForcePlayersStates([StateMgr::C_State_Playing]); CarRank::Update(CarRank::C_SortCriteria_BestRace); Race::EnableIntroDuringMatch(True); Race::SortScores(Race::C_Sort_BestRaceTime); declare netwrite Integer Net_SerialNeedToUpdate for Teams[0]; Net_SerialNeedToUpdate = 0; Map_TimeLimit = S_TimeLimit; Map_MapStartTime = StartTime; Map_MapsPerMatch = S_MapsPerMatch; Map_MalusTimeForDNF = S_MalusTimeForDNF; Map_UseTheWorstTimeForDNF = S_UseTheWorstTimeForDNF; UpdateScoresTableFooterAndTimeLimit(StartTime, S_TimeLimit, S_MapsPerMatch); declare Integer WorstTime for This; WorstTime = S_TimeLimit * 1000; UpdateCustomRanking(Null); *** ***Match_PlayLoop*** *** foreach (Event in PendingEvents) { Log::Log("[PlayLoop][PendingEvents] Event.Type: " ^ Event.Type); if (Event.Type == CSmModeEvent::EType::OnPlayerAdded) { if (Event.Player != Null) { Log::Log("[PlayLoop][PendingEvents] New player added: " ^ Event.Player.User.Name); UpdateCustomRanking(Event.Player); } } } // Manage race events declare RacePendingEvents = Race::GetPendingEvents(); foreach (Event in RacePendingEvents) { Race::ValidEvent(Event); // Waypoint if (Event.Type == Events::C_Type_Waypoint) { if (Event.Player != Null) { declare Better = False; declare OldRank = 0; if (Event.IsEndRace) { // Computes old rank before changing Score foreach (Index => Score in Scores) { if (Score.Id == Event.Player.Score.Id) { if (Score.BestRaceTimes.count != 0) { OldRank = Index + 1; } else { OldRank = -123; } break; } } // Change Score Better = Scores::UpdatePlayerBestRaceIfBetter(Event.Player); declare BetterLap = Scores::UpdatePlayerBestLapIfBetter(Event.Player); Scores::UpdatePlayerPrevRace(Event.Player); } else if (Event.IsEndLap) { declare BetterLap = Scores::UpdatePlayerBestLapIfBetter(Event.Player); if (Race::IsIndependentLaps()) { // Computes old rank before changing Score foreach (Index => Score in Scores) { if (Score.Id == Event.Player.Score.Id) { if (Score.BestLapTimes.count != 0) { OldRank = Index + 1; } else { OldRank = -123; } break; } } // Change Score Better = BetterLap; if (Better) Scores::UpdatePlayerBestRace(Event.Player); Scores::UpdatePlayerPrevLap(Event.Player); } } if (Better) { UpdateCustomRanking(Event.Player); declare NewRank = 0; foreach (Index => Score in Scores) { if (Score.Id == Event.Player.Score.Id) { NewRank = Index + 1; break; } } if (OldRank != NewRank) { if (0 < NewRank && NewRank < 4) { foreach (Player in AllPlayers) { UIModules_DisplayMessage::SendLiveMessage_RankUpdate(Player, Event.Player.User, NewRank); } } else if (0 < NewRank) { UIModules_DisplayMessage::SendLiveMessage_RankUpdate(Event.Player, Event.Player.User, NewRank); } } CarRank::ThrottleUpdate(CarRank::C_SortCriteria_BestRace); } } } } // 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 foreach (Player in Players) { if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned && Race::IsReadyToStart(Player)) { Race::Start(Player); } } } // Update the map duration setting if (Map_TimeLimit != S_TimeLimit || Map_MalusTimeForDNF != S_MalusTimeForDNF || Map_MapsPerMatch != S_MapsPerMatch || Map_UseTheWorstTimeForDNF != S_UseTheWorstTimeForDNF) { UpdateWorstTime(); UpdateCustomRanking(Null); Map_UseTheWorstTimeForDNF = S_UseTheWorstTimeForDNF; Map_MalusTimeForDNF = S_MalusTimeForDNF; Map_TimeLimit = S_TimeLimit; Map_MapsPerMatch = S_MapsPerMatch; UpdateScoresTableFooterAndTimeLimit(StartTime, S_TimeLimit, S_MapsPerMatch); } if (Net_ScriptEnvironment != S_ScriptEnvironment) { Net_ScriptEnvironment = S_ScriptEnvironment; } // End the map when time limit is reached if (EndTime > 0 && Now >= EndTime) { MB_StopMap(); } *** ***Match_EndRound*** *** if (Semver::Compare(XmlRpc::GetApiVersion(), ">=", "2.1.1")) { Scores::XmlRpc_SendScores(Scores::C_Section_PreEndRound, ""); } UpdateCustomRanking(Null); EndTime = -1; declare Integer WorstTime for This; declare Integer CumulateWorstTimeOfPreviousMaps for This; CumulateWorstTimeOfPreviousMaps = CumulateWorstTimeOfPreviousMaps + WorstTime + S_MalusTimeForDNF; Match_MatchDuration = ML::Max(0, Now - Map_MapStartTime); StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]); Race::EnableIntroDuringMatch(False); Race::SortScores(Race::C_Sort_BestRaceTime); TrophyRanking::UpdateUsersRank(); CarRank::Update(CarRank::C_SortCriteria_BestRace); Race::StopSkipOutroAll(); Scores::EndRound(); *** ***Match_EndMap*** *** if (MB_GetMapCount() >= S_MapsPerMatch) { Log::Log("[Match_EndMap] Stop Match"); Scores::SetPlayerWinner(Scores::GetBestPlayer(Scores::C_Sort_MatchPoints, Scores::C_Order_Ascending)); MB_StopMatch(); } else { Scores::SetPlayerWinner(Scores::GetBestPlayer(GetLadderSortCriteria())); } *** ***Match_BeforePodiumSequence*** *** UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::EndRound; UIManager.UIAll.BigMessageSoundVariant = 0; declare CSmScore WinnerScore <=> Scores::GetPlayerWinner(); if (WinnerScore != Null) { if (!MB_MatchIsRunning()) { UIModules_BigMessage::SetMessage("$<%1$> wins the match!", WinnerScore.User.WebServicesUserId); } else { UIModules_BigMessage::SetMessage("$<%1$> wins the map!", WinnerScore.User.WebServicesUserId); } } else { UIModules_BigMessage::SetMessage(_("|Match|Draw")); } // Compute match trophies Trophy_LiveTimeAttackAchievement_ClearResultList(); foreach (Key => Score in Scores) { if (TrophyRanking::UserIsRanked(Score.User) && Score.BestRaceTimes.count > 0 && Score.BestRaceTimes[Score.BestRaceTimes.count - 1] >= 0) { Trophy_LiveTimeAttackAchievement_AddResult(Score.User.WebServicesUserId, Key + 1, TrophyRanking::GetUserRank(Score.User)); } } Match_TrophyTask = Task::Create(This, Trophy_LiveTimeAttackAchievement_SendResultList(Match_MatchDuration / 1000)); Match_TrophyTaskEndTime = Now + C_TrophyTaskTimeout; UIModules_ScoresTable::ResetTrophies(); UIModules_EndMatchTrophy::ResetTrophyAnimation(); *** ***Match_PodiumSequence*** *** declare CUIConfig::EUISequence PrevUISequence = UIManager.UIAll.UISequence; UIManager.UIAll.UISequence = CUIConfig::EUISequence::Podium; MB_Private_Sleep((S_ChatTime*1000)/2); // Wait until the trophy task is complete Match_TrophyTask = Task::Update(Match_TrophyTask); while (Task::IsInitialized(Match_TrophyTask) && Task::IsRunning(Match_TrophyTask) && Now < Match_TrophyTaskEndTime) { MB_Yield(); Match_TrophyTask = Task::Update(Match_TrophyTask); } if (Task::IsInitialized(Match_TrophyTask)) { Match_TrophyTask = Task::Update(Match_TrophyTask); declare Integer[Integer][Text] AccountsTrophies; if (!Task::IsRunning(Match_TrophyTask)) { declare CTaskResult_AccountTrophyGainList SourceTask = Task::GetSourceTask_AccountTrophyGainList(Match_TrophyTask); if (Task::IsSuccess(Match_TrophyTask) && SourceTask != Null) { foreach (AccountTrophyGain in SourceTask.AccountTrophyGainList) { declare Integer[Integer] AccountTrophies = UIModules_ScoresTable::ConvertAccountTrophyGain(AccountTrophyGain); if (AccountTrophies.count > 0) { AccountsTrophies[AccountTrophyGain.WebServicesUserId] = AccountTrophies; } } } } Match_TrophyTask = Task::Destroy(Match_TrophyTask); UIModules_ScoresTable::SetTrophies(AccountsTrophies); UIModules_EndMatchTrophy::PlayTrophyAnimation(AccountsTrophies); StateMgr::ForcePlayersStates([StateMgr::C_State_TrophyAnimation]); MB_Private_Sleep(C_TrophyAnimationDuration); StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]); } UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible; MB_Private_Sleep((S_ChatTime*1000)/2); SetScoresTableScoreMode(Race::IsIndependentLaps(), True); MB_Private_Sleep(C_TrophyDisplayDuration); SetScoresTableScoreMode(Race::IsIndependentLaps(), False); UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal; UIManager.UIAll.UISequence = PrevUISequence; *** ***Match_AfterPodiumSequence*** *** UIModules_BigMessage::SetMessage(""); UIModules_ScoresTable::ResetTrophies(); *** ***Match_BeforeUnloadMap*** *** Match_CanForceTrophyRankUpdate = False; *** // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Functions // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // /// Select the scores table score mode Void SetScoresTableScoreMode(Boolean _IsIndependentLaps, Boolean _DisplayTrophies) { if (_DisplayTrophies) UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_Trophy); else if (_IsIndependentLaps) UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_LapTime); else UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_BestTime); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // /** Update the time limit * * @param _StartTime The starting time of the map * @param _NewTimeLimit The time limit before going to the next map */ Void UpdateScoresTableFooterAndTimeLimit(Integer _StartTime, Integer _NewTimeLimit, Integer _MapsPerMatch) { Log::Log("[UpdateScoresTableFooterAndTimeLimit] Update Settings Footer"); declare Text[] Parts; declare Text Message = ""; if (_NewTimeLimit <= 0) { if (Parts.count > 0) Message ^= "\n"; Message ^= """%{{{Parts.count + 1}}} -"""; //L16N [Rounds] Number of points to reach to win the match. Parts.add(_("Time Limit : ")); EndTime = -1; UIModules_ScoresTable::SetFooterInfo(TL::Compose("%1 -", _("Time Limit"))); } else { if (Parts.count > 0) Message ^= "\n"; Message ^= """%{{{Parts.count + 1}}}{{{TL::TimeToText(_NewTimeLimit*1000)}}}"""; //L16N [Rounds] Number of points to reach to win the match. Parts.add(_("Time Limit : ")); EndTime = _StartTime + (S_TimeLimit * 1000); UIModules_ScoresTable::SetFooterInfo(TL::Compose("%1 "^TL::TimeToText(_NewTimeLimit*1000), _("Time Limit"))); } if (_MapsPerMatch > 0) { if (Parts.count > 0) Message ^= "\n"; 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])); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // /** Get the right sort criteria for * the scores * * @return The sort criteria */ Integer GetScoresSortCriteria() { if (Race::IsIndependentLaps()) return Race::C_Sort_BestLapTime; return Race::C_Sort_BestRaceTime; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // /** Get the right sort criteria for * the ladder * * @return The sort criteria */ Integer GetLadderSortCriteria() { if (Race::IsIndependentLaps()) return Scores::C_Sort_BestLapTime; return Scores::C_Sort_BestRaceTime; } Void UpdateWorstTime() { declare Integer MaxTime; if (S_UseTheWorstTimeForDNF) { foreach (Score in Scores) { MaxTime = ML::Max(MaxTime, Scores::GetPlayerBestRaceTime(Score)); } if (MaxTime == 0) MaxTime = S_TimeLimit * 1000; } else { MaxTime = S_TimeLimit * 1000; } declare Integer WorstTime for This; WorstTime = MaxTime; } /** Update the Scores Table with hidden custom points * * @param _EliminatedPlayer The Player who is eliminated */ Void UpdateCustomRanking(CSmPlayer _Player) { /* * FYI, it's not possible to sort the scoreboard by Matchpoint Asc */ declare netwrite Integer[Text] Net_MatchPoints for Teams[0]; declare netwrite Integer[Text] Net_RoundPoints for Teams[0]; declare Integer[Text] MatchPoints = Net_MatchPoints; declare Integer[Text] RoundPoints = Net_RoundPoints; declare Boolean SkipUpdate; declare Text[][Text] CustomPoints for This; declare Integer WorstTime for This; foreach (Score in Scores) { if (Score == Null) continue; declare CSmPlayer Player = GetPlayer(Score.User.Login); if (Player == Null) continue; if (_Player == Null || _Player == Player) { declare Text BestCurrentColor = "$ccc"; if (Score.BestRaceTimes.count > 0) { if (S_UseTheWorstTimeForDNF && Scores::GetPlayerRoundPoints(Score) == 0 || (WorstTime == Scores::GetPlayerRoundPoints(Score) && Scores::GetPlayerRoundPoints(Score) != Scores::GetPlayerBestRaceTime(Score)) || // When Last (WorstTime + S_MalusTimeForDNF == Scores::GetPlayerRoundPoints(Score) && Scores::GetPlayerRoundPoints(Score) != Scores::GetPlayerBestRaceTime(Score) + S_MalusTimeForDNF)) // When First Time registered { Log::Log("[UpdateCustomRanking] Update Worst Time"); UpdateWorstTime(); Scores::SetPlayerRoundPoints(Score, Scores::GetPlayerBestRaceTime(Score)); UpdateCustomRanking(Null); SkipUpdate = True; break; } else { Scores::SetPlayerRoundPoints(Score, Scores::GetPlayerBestRaceTime(Score)); } BestCurrentColor = "$fff" ; } else { Scores::SetPlayerRoundPoints(Score, WorstTime + S_MalusTimeForDNF); } declare Text BestCurrentTime = TL::TimeToText(Scores::GetPlayerRoundPoints(Score) , True, True); declare Text TotalTimes = TL::TimeToText(Scores::GetPlayerRoundPoints(Score) + Scores::GetPlayerMatchPoints(Score) , True, False); RoundPoints[Score.User.WebServicesUserId] = Scores::GetPlayerRoundPoints(Score); MatchPoints[Score.User.WebServicesUserId] = Scores::GetPlayerRoundPoints(Score) + Scores::GetPlayerMatchPoints(Score); CustomPoints[Score.User.WebServicesUserId] = ["$<$i"^ BestCurrentColor ^ BestCurrentTime ^ "$> $<$n$i$aaa" ^ TotalTimes , ""]; } } if (!SkipUpdate) { UIModules_ScoresTable::SetCustomPoints(CustomPoints); declare netwrite Integer Net_SerialNeedToUpdate for Teams[0]; Net_SerialNeedToUpdate = Net_SerialNeedToUpdate + 1; // Update UI Net_MatchPoints = MatchPoints; Net_RoundPoints = RoundPoints; } } Void SetML() { declare Text MLText = """