/** * LastManStanding mode */ #Extends "Modes/Nadeo/Trackmania/Base/TrackmaniaRoundsBase.Script.txt" #Const CompatibleMapTypes "TrackMania\\TM_Race,TM_Race" #Const Version "2024-05-08" #Const ScriptName "Modes/TM2020-Gamemodes/LastManStanding.Script.txt" // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Libraries // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // #Include "TextLib" as TL #Include "MathLib" as ML #Include "Libs/Nadeo/Trackmania/Modes/Rounds/StateManager.Script.txt" as StateMgr #Include "Libs/Nadeo/Trackmania/MainMenu/Constants.Script.txt" as MenuConsts #Include "Libs/Nadeo/CMGame/Modes/Utils.Script.txt" as ModeUtils // UI from Race #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/TMGame/Modes/Base/UIModules/BigMessage_Server.Script.txt" as UIModules_BigMessage // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Settings // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // #Setting S_ForceLapsNb 1 #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 4 as "Only used if S_KeepScoresBetweenRounds = True" #Setting S_AFKIdleTime 120000 as "Time before being an AFK player will be kicked" #Setting S_IntroTime 5 as "Time of the map intro" #Setting S_TimeBeforeMalus 10 as "Time Before Malus" #Setting S_TimeBeforeNightmare 150 as "Time Before Nightmare" #Setting S_MalusEveryNSecs 10 as "Roll a new Malus every N Sec" #Setting S_NextMalusPreparationTime 10 as "Time given to players to prepare them before a Malus" #Setting S_MalusDuration 5 as "Malus Duration" #Setting S_KeepScoresBetweenRounds False #Setting S_TrustClientSimu False #Setting S_UseCrudeExtrapolation False // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Constants // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // #Const C_ModeName "LastManStanding" #Const Description "$zIn $<$t$6F9LastManStanding$> mode, The goal is to be the last player not to fall off the structure. Collisions are activated and you can push your opponents to win. From a certain time, malus are sent to all the players of the game to accelerate its end." #Const C_HudModulePath "" //< Path to the hud module #Const C_ManiaAppUrl "file://Media/ManiaApps/Nadeo/Trackmania/Modes/Rounds.Script.txt" //< Url of the mania app #Const C_FakeUsersNb 0 #Const C_Callback_CustomChat_ChatMessage "CustomChat.GamemodeChatMessage" #Const C_Malus_Reset 0 #Const C_Malus_ForceEngine 1 #Const C_Malus_BackwardOnly 2 #Const C_Malus_NoBrakes 3 #Const C_Malus_NoEngine 4 #Const C_Malus_NoSteer 5 #Const C_Malus_SlowMotion 6 #Const C_Malus_BoostDown 7 #Const C_Malus_BoostUp 8 #Const C_Malus_Boost2Down 9 #Const C_Malus_Boost2Up 10 #Const C_Malus_LockPlayer 11 #Const C_Malus_AccelCoef25 12 #Const C_Malus_AdherenceCoef25 13 #Const C_Malus_ControlCoef25 14 #Const C_Malus_GravityCoef25 15 #Const C_Malus_Nightmare 99 #Const C_Malus_Name [0 => "Reset", 1 => "ForceEngine", 2 => "BackwardOnly" , 3 => "NoBrakes", 4 => "NoEngine", 5 => "NoSteer", 6 => "SlowMotion", 7 => "BoostDown", 8 => "BoostUp", 9 => "SuperBoostDown", 10 => "SuperBoostUp", 11 => "LoseControl", 12 => "25% AccelCoef", 13 => "25% Adherence", 14 => "25% Control", 15 => "25% Gravity", 99 => "NightMare"] #Struct K_Malus { Integer Time; Integer MalusIndex; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Extends // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ***Match_LogVersions*** *** Log::RegisterScript(ScriptName, Version); Log::RegisterScript(StateMgr::ScriptName, StateMgr::Version); *** ***Match_LoadLibraries*** *** StateMgr::Load(); XmlRpc::RegisterCallback(C_Callback_CustomChat_ChatMessage, """ * Name: {{{C_Callback_CustomChat_ChatMessage}}} * Type: CallbackArray * Description: Gamemode Chat Message for the Custom Chat plugin * Data: - Version >=2.0.0: ``` [ "This is a gamemode chat message" ] ``` """); *** ***Match_UnloadLibraries*** *** StateMgr::Unload(); XmlRpc::UnregisterCallback(C_Callback_CustomChat_ChatMessage); *** ***Match_Settings*** *** MB_Settings_UseDefaultTimer = False; MB_Settings_UseDefaultHud = (C_HudModulePath == ""); Rounds_Settings_UseDefaultSpawnManagement = False; MB_Settings_UseDefaultIntroSequence = False; *** ***Match_Rules*** *** ModeInfo::SetName(C_ModeName); ModeInfo::SetType(ModeInfo::C_Type_FreeForAll); ModeInfo::SetRules(Description); ModeInfo::SetStatusMessage(_("TYPE: Free for all\nOBJECTIVE: Be the last player not to fall off the structure.")); *** ***Match_LoadHud*** *** if (C_HudModulePath != "") Hud_Load(C_HudModulePath); *** ***Match_AfterLoadHud*** *** UIManager.UIAll.ScoreTableOnlyManialink = True; ClientManiaAppUrl = C_ManiaAppUrl; Race::SortScores(Race::C_Sort_TotalPoints); UIModules_PauseMenu_Online::SetHelp(Description); UIManager.UIAll.OverlayHideSpectatorInfos = True; UIManager.UIAll.OverlayHideCountdown = True; Markers::SetDefaultMarker_HudVisibility(CUIConfigMarker::EHudVisibility::Always); UIManager.UIAll.LabelsVisibility = CUIConfig::EHudVisibility::Everything ; UIModules::UnloadModules(["UIModule_Race_LapsCounter"]); 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); } } } foreach (Event in UIManager.PendingEvents) { if (Event.CustomEventType == "LMS_NotifyAFK") { if (Event.CustomEventData.count > 0) { UIManager.UIAll.SendChat("$ff9" ^ Event.CustomEventData[0] ^ " has been kicked for being AFK"); XmlRpc::SendCallback(C_Callback_CustomChat_ChatMessage, ["$ff9" ^ Event.CustomEventData[0] ^ " has been kicked for being AFK"]); } } } StateMgr::Yield(); *** ***Match_StartServer*** *** // Initialize mode Clans::SetClansNb(0); UsePvPCollisions = True; UsePvECollisions = True; Scores::SaveInScore(Scores::C_Points_Match); StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]); WarmUp::SetAvailability(True); CarRank::Reset(); ResetNetworkVariables(); *** ***Match_InitMap*** *** ResetNetworkVariables(); ResetCustomPoints(); // Map Intro declare Boolean MapIsCompatible; declare CMapLandmark[] Landmarks = Map::GetFinishesAndMultilaps(); foreach (Landmark in Landmarks) { if (Map::IsMultilap(Landmark)) { MapIsCompatible = True; break; } } if (!MapIsCompatible) { UIManager.UIAll.QueueMessage(3000, 1, CUIConfig::EMessageDisplay::Big, _("This map is not valid")); MB_Sleep(3000); MB_StopMap(); MB_SetValidMap(False); } else if (S_IntroTime > 0) { declare netwrite Boolean Net_LMS_IsIntro for Teams[0] = False; Net_LMS_IsIntro = True; StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]); if (Map.HasCustomIntro) { ModeUtils::PushAndApplyUISequence(UIManager.UIAll, CUIConfig::EUISequence::RollingBackgroundIntro); } else { ModeUtils::PushAndApplyUISequence(UIManager.UIAll, CUIConfig::EUISequence::Playing); } if (MB_MapIsRunning() && AllPlayers.count <= 0) MB_Yield(); declare Integer WaitingScreenDuration = 0; while (MB_MapIsRunning() && S_IntroTime - WaitingScreenDuration > 0) { WaitingScreenDuration = WaitingScreenDuration + 1; MB_Sleep(1000); } ModeUtils::PopAndApplyUISequence(UIManager.UIAll); Net_LMS_IsIntro = False; } *** ***Match_StartMap*** *** Race::SetRespawnBehaviour(Race::C_RespawnBehaviour_AlwaysGiveUp); CarRank::Reset(); *** ***Match_InitRound*** *** declare Integer Round_TimeBeforeMalus; declare Integer Round_TimeBeforeNightmare; declare Integer Round_MalusEveryNSecs; declare Integer Round_NextMalusPreparationTime; declare Integer Round_MalusDuration; declare Integer Round_RoundsPerMap; declare Text[] Round_AccountIdsOfEliminated; declare Text[] LMS_AccountIdsOfPlayers for This = []; declare Integer LMS_LandmarkIndex for This = 0; declare netwrite Boolean Net_DisplayUI for Teams[0] = False; declare netwrite Integer Net_NBPlayers for Teams[0] = 0; declare netwrite Integer Net_PlayersNbAlive for Teams[0] = 0; declare netwrite Integer Net_NextMalus for Teams[0] = -1; declare netwrite Integer Net_TimeBeforeMalus for Teams[0] = -1; declare netwrite Integer Net_RoundsPerMap for Teams[0] = 0; declare netwrite Integer Net_CurrentRoundNb for Teams[0] = 0; declare Boolean Round_ThrottleUpdate = False; declare Boolean Round_ActiveMalus = False; declare Boolean Round_PendingMalus = False; declare Integer Round_NextStepMalusTime = 0; declare Integer Round_MalusIndex = 0; declare Integer Round_MalusTime = 0; declare K_Malus[Text] Round_MalusQueue; *** ***Match_StartRound*** *** UIModules_ScoresTable::SetScoreMode(UIModules_ScoresTable::C_Mode_Points); Race::SortScores(Race::C_Sort_RoundPoints); declare netwrite Integer Net_LMS_AFKIdleTime for Teams[0] = 120000; Net_LMS_AFKIdleTime = S_AFKIdleTime; // WorkAround for longloading declare Integer StartMapTime = Now; while (Players.count < 2 && Now < (StartMapTime + 3000)) { MB_Yield(); } // Initialize race StartTime = Now + Race::C_SpawnDuration; Round_TimeBeforeMalus = S_TimeBeforeMalus; Round_TimeBeforeNightmare = S_TimeBeforeNightmare; Round_MalusEveryNSecs = S_MalusEveryNSecs; Round_NextMalusPreparationTime = S_NextMalusPreparationTime; Round_MalusDuration = S_MalusDuration; Round_RoundsPerMap = S_RoundsPerMap; UpdateScoresTableFooter(); Round_MalusTime = GetTimeBeforeMalus(StartTime, S_TimeBeforeMalus, S_TimeBeforeNightmare); Net_DisplayUI = True; Net_TimeBeforeMalus = Round_MalusTime; Net_NextMalus = -1; Net_RoundsPerMap = Round_RoundsPerMap; Net_CurrentRoundNb = MB_GetValidRoundCount(); ResetCustomPoints(); // Spawn players for the race ---Rounds_CanSpawn--- declare Text[] LMS_AccountIdsOfPlayers for This = []; declare CMapLandmark PlayerLM; declare Integer LMS_LandmarkIndex for This = 0; LMS_AccountIdsOfPlayers = []; // Suffle Players list to randomise spawn declare CSmPlayer[Integer] ShuffledPlayers; foreach (Player in Players) { declare Integer RandomIndex = 0; while (RandomIndex == 0 || ShuffledPlayers.existskey(RandomIndex)) { RandomIndex = ML::Rand(1, 10000); } ShuffledPlayers[RandomIndex] = Player; } ShuffledPlayers = ShuffledPlayers.sortkey(); Net_NBPlayers = ShuffledPlayers.count; Net_PlayersNbAlive = Net_NBPlayers; foreach (Player in ShuffledPlayers) { if (Player == Null) continue; PlayerLM = Null; while (PlayerLM == Null) { if (LMS_LandmarkIndex > Landmarks.count - 1 ) { LMS_LandmarkIndex = 0; } if (Map::IsMultilap(Landmarks[LMS_LandmarkIndex])) { PlayerLM = Landmarks[LMS_LandmarkIndex]; } LMS_LandmarkIndex = LMS_LandmarkIndex + 1 ; } Race::Start(Player, PlayerLM , StartTime); CarRank::SetRank(Player, Net_NBPlayers); LMS_AccountIdsOfPlayers.add(Player.User.WebServicesUserId); Round_MalusQueue[Player.User.Login] = GetNewMalus(C_Malus_Reset, 1500); } UIModules_ScoresTable::DisplayOnly(LMS_AccountIdsOfPlayers); StateMgr::ForcePlayersStates([StateMgr::C_State_Playing]); Race::EnableIntroDuringMatch(False); UIManager.UIAll.SendChat("$<$ff3$> Stay the most time on the structure. $<$ff9GL HF!$>"); XmlRpc::SendCallback(C_Callback_CustomChat_ChatMessage, ["$<$ff3$> Stay the most time on the structure. $<$ff9GL HF!$>"]); *** // @mslint-disable-next-line max-statements ***Match_PlayLoop*** *** // Manage race events declare Events::K_RaceEvent[] RacePendingEvents = Race::GetPendingEvents(); foreach (Event in RacePendingEvents) { Race::ValidEvent(Event); if (Event.Player == Null) continue; switch (Event.Type) { case Events::C_Type_Waypoint: { Round_ThrottleUpdate = True; Scores::UpdatePlayerBestRaceIfBetter(Event.Player); Race::StopSkipOutro(Event.Player); UpdateCustomRanking(Event.Player.User, Event.Player, False); if (Event.Player.User != Null) Round_AccountIdsOfEliminated.add(Event.Player.User.WebServicesUserId); } case Events::C_Type_GiveUp: { Round_ThrottleUpdate = True; UpdateCustomRanking(Event.Player.User, Event.Player, True); if (Event.Player.User != Null) Round_AccountIdsOfEliminated.add(Event.Player.User.WebServicesUserId); } case Events::C_Type_Eliminated: { Round_ThrottleUpdate = True; Race::StopSkipOutro(Event.Player); UpdateCustomRanking(Event.Player.User, Event.Player, True); if (Event.Player.User != Null) Round_AccountIdsOfEliminated.add(Event.Player.User.WebServicesUserId); } } } // Manage mode events foreach (Event in PendingEvents) { if (Event.HasBeenPassed || Event.HasBeenDiscarded) continue; Events::Invalid(Event); if (Event.Type == CSmModeEvent::EType::OnPlayerRemoved) { if (Event.User == Null ) continue; if (!LMS_AccountIdsOfPlayers.exists(Event.User.WebServicesUserId)) continue; if (Round_AccountIdsOfEliminated.exists(Event.User.WebServicesUserId)) continue; Round_ThrottleUpdate = True; UpdateCustomRanking(Event.User, Null, True); Round_AccountIdsOfEliminated.add(Event.User.WebServicesUserId); } } // Detect when a players count changed without having triggered any of Event (when becoming spectator for example) if (!Round_ThrottleUpdate && Net_PlayersNbAlive != PlayersNbAlive) { log("Trying to detect why the player count changed"); foreach (Player in AllPlayers) { if (Player.User == Null || Player.Score == Null) continue; if (!LMS_AccountIdsOfPlayers.exists(Player.User.WebServicesUserId)) continue; if (Player.SpawnStatus != CSmPlayer::ESpawnStatus::NotSpawned) continue; if (Round_AccountIdsOfEliminated.exists(Player.User.WebServicesUserId)) continue; UpdateCustomRanking(Player.User, Player, True); Round_AccountIdsOfEliminated.add(Player.User.WebServicesUserId); Round_ThrottleUpdate = True; } } if (Round_ThrottleUpdate) { Round_ThrottleUpdate = False; Net_PlayersNbAlive = PlayersNbAlive; declare Integer Points = Net_NBPlayers - Net_PlayersNbAlive; foreach (Player in Players) { CarRank::SetRank(Player, PlayersNbAlive); if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned) { Scores::SetPlayerRoundPoints(Player.Score, Points); } } } if (PlayersNbAlive <= 1 && LMS_AccountIdsOfPlayers.count >= 2) { //TODO just respawn in case of 1 player Net_TimeBeforeMalus = -1; MB_StopRound(); } // Update the map duration setting if (Round_TimeBeforeMalus != S_TimeBeforeMalus || Round_TimeBeforeMalus != S_TimeBeforeNightmare || Round_MalusEveryNSecs != S_MalusEveryNSecs || Round_NextMalusPreparationTime != S_NextMalusPreparationTime || Round_MalusDuration != S_MalusDuration || Round_RoundsPerMap != S_RoundsPerMap) { Round_TimeBeforeMalus = S_TimeBeforeMalus; Round_TimeBeforeNightmare = S_TimeBeforeNightmare; Round_MalusEveryNSecs = S_MalusEveryNSecs; Round_NextMalusPreparationTime = S_NextMalusPreparationTime; Round_MalusDuration = S_MalusDuration; Round_RoundsPerMap = S_RoundsPerMap; Net_RoundsPerMap = Round_RoundsPerMap; UpdateScoresTableFooter(); Round_MalusTime = GetTimeBeforeMalus(StartTime, S_TimeBeforeMalus, S_TimeBeforeNightmare); if (Round_NextStepMalusTime == 0) { Net_TimeBeforeMalus = Round_MalusTime; } if (Round_MalusDuration <= 0 || (Round_TimeBeforeMalus < 0 && Round_TimeBeforeNightmare < 0)) { Net_TimeBeforeMalus = -1; Net_NextMalus = -1; } } // Run Malus if (Players.count > 0 && S_MalusDuration > 0 && Round_MalusTime != -1 && Now > Round_MalusTime) { if (Now > Round_NextStepMalusTime) { if (!Round_ActiveMalus && !Round_PendingMalus) { if (S_TimeBeforeNightmare >= 0 && Now > (StartTime + (S_TimeBeforeNightmare * 1000))) { Round_MalusIndex = C_Malus_Nightmare; } else if (AllPlayersAreInTurtle()) { log("All players are in turtle"); Round_MalusIndex = ML::Rand(7, 10); // Boost if all players in Turtle } else { Round_MalusIndex = ML::Rand(1, 15); } Round_PendingMalus = True; Round_ActiveMalus = False; Round_NextStepMalusTime = Now + (S_NextMalusPreparationTime*1000); // Players UI update Net_NextMalus = Round_MalusIndex; Net_TimeBeforeMalus = Round_NextStepMalusTime; } else if (Round_PendingMalus && !Round_ActiveMalus) { foreach (Player in Players) { Round_MalusQueue[Player.User.Login] = GetNewMalus(Round_MalusIndex); } Round_PendingMalus = False; Round_ActiveMalus = True; Round_NextStepMalusTime = Now + (S_MalusDuration*1000); UIModules_BigMessage::SetMessage("Current Effect: "^C_Malus_Name[Round_MalusIndex]); // Players UI update Net_NextMalus = 0; Net_TimeBeforeMalus = Round_NextStepMalusTime; } else if (!Round_PendingMalus && Round_ActiveMalus) { if (Round_MalusIndex == 99) { foreach (Player in Players) { Round_MalusQueue[Player.User.Login] = GetNewMalus(C_Malus_Nightmare); } Round_NextStepMalusTime = Now + (S_MalusDuration*1000); } else { foreach (Player in Players) { Round_MalusQueue[Player.User.Login] = GetNewMalus(C_Malus_Reset); } Round_PendingMalus = False; Round_ActiveMalus = False; Round_NextStepMalusTime = Now + (S_MalusEveryNSecs*1000); UIModules_BigMessage::SetMessage(""); // Players UI update Net_NextMalus = -1; Net_TimeBeforeMalus = Round_NextStepMalusTime; } } } } foreach (Login => Malus in Round_MalusQueue) { declare CSmPlayer Player = GetPlayer(Login); if (Malus.Time + 1000 < Now) { // Clear old entry Round_MalusQueue.removekey(Login); } else if (Player != Null && Malus.Time <= Now && (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned || Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawning)) { Log::Log("[ApplyPhysics] Trying to set Event " ^ C_Malus_Name[Malus.MalusIndex] ^ " for " ^ Player.User.Name); if (SetMalus(Player, Malus.MalusIndex)) { Round_MalusQueue.removekey(Login); } } } *** ***Match_EndRound*** *** UIModules_BigMessage::SetMessage(""); Net_DisplayUI = False; Net_TimeBeforeMalus = -1; Net_NextMalus = -1; Race::StopSkipOutroAll(); StateMgr::ForcePlayersStates([StateMgr::C_State_Waiting]); if (Round_ForceEndRound || Round_SkipPauseRound) { // 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(); } MB_SetValidRound(False); } else { if (S_KeepScoresBetweenRounds) { UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible; MB_Sleep((S_ChatTime*1000)/2); ComputeRoundPoints(); UIModules_ScoresTable::SetCustomPoints([]); UIModules_ScoresTable::DisplayRoundPoints(True); MB_Sleep((S_ChatTime*1000)/2); Scores::EndRound(); Race::SortScores(Race::C_Sort_TotalPoints); MB_Sleep((S_ChatTime*1000)/2); } else { declare CSmScore WinnerScore <=> Scores::GetBestPlayer(Scores::C_Sort_RoundPoints); if (WinnerScore == Null) { foreach (Score in Scores) { if (Score.BestRaceTimes.count <= 0 && Score.User != Null && LMS_AccountIdsOfPlayers.exists(Score.User.WebServicesUserId)) { declare CSmPlayer Player = GetPlayer(Score.User.Login); if (Player != Null && !Player.RequestsSpectate) { WinnerScore <=> Score; break; } } } } Scores::SetPlayerWinner(WinnerScore); ModeUtils::PlaySound(CUIConfig::EUISound::EndRound, 0); if (WinnerScore == Null) { UIModules_BigMessage::SetMessage(_("|Match|Draw")); } else { UIModules_BigMessage::SetMessage(_("$<%1$> wins the match!"), WinnerScore.User.WebServicesUserId); } Scores::EndRound(); Race::SortScores(Race::C_Sort_TotalPoints); MB_Sleep((S_ChatTime*1000)/2); UIModules_BigMessage::SetMessage(""); UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible; MB_Sleep((S_ChatTime*1000)/2); Scores::Clear(); } UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal; UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing; UIModules_ScoresTable::DisplayRoundPoints(False); UIModules_ScoresTable::SetCustomPoints([]); if (MapIsOver()) MB_StopMap(); } *** ***Match_EndMap*** *** if (MatchIsOver()) { MB_StopMatch(); if (!S_KeepScoresBetweenRounds) MB_SkipPodiumSequence(); declare CSmScore Winner <=> Scores::GetBestPlayer(Scores::C_Sort_MatchPoints); Scores::SetPlayerWinner(Winner); } else { MB_SkipPodiumSequence(); } *** // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Functions // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Reset Network Variables Void ResetNetworkVariables() { declare netwrite Boolean Net_DisplayUI for Teams[0] = False; Net_DisplayUI = False; declare netwrite Integer Net_NBPlayers for Teams[0] = 0; Net_NBPlayers = 0; declare netwrite Integer Net_PlayersNbAlive for Teams[0] = 0; Net_PlayersNbAlive = 0; declare netwrite Integer Net_NextMalus for Teams[0] = -1; Net_NextMalus = 0; declare netwrite Integer Net_TimeBeforeMalus for Teams[0] = -1; Net_TimeBeforeMalus = 0; declare netwrite Integer Net_RoundsPerMap for Teams[0] = 0; Net_RoundsPerMap = 0; declare netwrite Integer Net_CurrentRoundNb for Teams[0] = 0; Net_CurrentRoundNb = 0; } Void ResetCustomPoints() { declare Text[][Text] CustomPoints = []; foreach (Score in Scores) { if (Score.User == Null) continue; CustomPoints[Score.User.WebServicesUserId] = ["--:--.---"]; } UIModules_ScoresTable::SetCustomPoints(CustomPoints); } /** Detect if all players are in Turtle * * @return Return False if one player is not in turtle */ Boolean AllPlayersAreInTurtle() { foreach (Player in Players) { if (Player.WheelsContactCount > 1 || Player.Speed > 1) { return False; } } return True; } /** Get the Time Before the first Malus or NightMare Mode * * @param _StartTime The starting time of the map * @param _TimeBeforeMalus The time before the first Malus * @param _TimeBeforeNightmare The time before the NightMare mode * * @return The Malus Time or -1 in no Malus */ Integer GetTimeBeforeMalus(Integer _StartTime, Integer _TimeBeforeMalus, Integer _TimeBeforeNightmare) { declare Integer MalusTime; if (_TimeBeforeMalus >= 0 && (_TimeBeforeMalus < _TimeBeforeNightmare || _TimeBeforeNightmare == -1)) { MalusTime = _StartTime + (_TimeBeforeMalus * 1000); } else if (_TimeBeforeNightmare >= 0 && (_TimeBeforeNightmare < _TimeBeforeMalus || _TimeBeforeNightmare == _TimeBeforeMalus || _TimeBeforeMalus == -1)) { MalusTime = _StartTime + (_TimeBeforeNightmare * 1000); } else { MalusTime = -1; } return MalusTime; } /** Update the Scores Table with hidden custom points * * @param _User The User who is eliminated * @param _Player The Player who is eliminated. Can be Null * @param _OverrideTime Compute time because it not ended the Map */ Void UpdateCustomRanking(CUser _User, CSmPlayer _Player, Boolean _OverrideTime) { if (_User == Null) return; declare Text[][Text] CustomPoints = UIModules_ScoresTable::GetCustomPoints(); if (_OverrideTime || _Player == Null || _Player.RaceWaypointTimes.count == 0) { CustomPoints[_User.WebServicesUserId] = [TL::TimeToText(Now - StartTime, True, True)]; } else { CustomPoints[_User.WebServicesUserId] = [TL::TimeToText(_Player.RaceWaypointTimes[-1], True, True)]; } UIModules_ScoresTable::SetCustomPoints(CustomPoints); UIManager.UIAll.SendChat("""$<$ff3$> Player $<$ff9{{{_User.Name}}}$> is eliminated"""); XmlRpc::SendCallback(C_Callback_CustomChat_ChatMessage, ["""$<$ff3$> Player $<$ff9{{{_User.Name}}}$> is eliminated"""]); } /** Distribute real RoundPoints using Points Repartition * Only used when S_KeepScoresBetweenRounds = True */ Void ComputeRoundPoints() { declare Integer[] PointsRepartition = PointsRepartition::GetPointsRepartition(); foreach (Key => Score in Scores) { declare Integer Points = 0; if (PointsRepartition.count > 0) { if (PointsRepartition.existskey(Key)) { Points = PointsRepartition[Key]; } else { Points = PointsRepartition[PointsRepartition.count - 1]; } } Scores::SetPlayerRoundPoints(Score, Points); } } /** Update the scores table footer text * */ Void UpdateScoresTableFooter() { declare Text Footer = ""; if (S_MalusDuration <= 0 || (S_TimeBeforeMalus < 0 && S_TimeBeforeNightmare < 0)) { Footer ^= "Malus disabled"; } else { declare Text[] Parts; declare Text Message = ""; if (S_TimeBeforeMalus >= 0) { if (Parts.count > 0) Message ^= "\n"; Message ^= """%{{{Parts.count + 1}}}{{{TL::TimeToText(S_TimeBeforeMalus*1000)}}}"""; Parts.add("Time Before Malus: "); } if (S_TimeBeforeNightmare >= 0) { if (Parts.count > 0) Message ^= "\n"; Message ^= """%{{{Parts.count + 1}}}{{{TL::TimeToText(S_TimeBeforeNightmare*1000)}}}"""; Parts.add("Time Before NM: "); } if (S_KeepScoresBetweenRounds) { if (Parts.count > 0) Message ^= "\n"; Message ^= """%{{{Parts.count + 1}}}{{{MB_GetMapCount()}}}/{{{S_MapsPerMatch}}}"""; //L16N [Rounds] Number of maps played during the match. Parts.add(_("Maps : ")); } switch (Parts.count) { case 0: Footer = Message; case 1: Footer = TL::Compose(Message, Parts[0]); case 2: Footer = TL::Compose(Message, Parts[0], Parts[1]); case 3: Footer = TL::Compose(Message, Parts[0], Parts[1], Parts[2]); } } UIModules_ScoresTable::SetFooterInfo(Footer); } K_Malus GetNewMalus(Integer _MalusIndex, Integer _Time) { return K_Malus { Time = Now + _Time, MalusIndex = _MalusIndex }; } K_Malus GetNewMalus(Integer _MalusIndex) { return GetNewMalus(_MalusIndex, 0); } /** Set Malus to a specific Players * * @param _Player Player * @param _Type Malus Index */ Boolean SetMalus(CSmPlayer _Player, Integer _Type) { if (_Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned && !SetPlayer_DelayedIsFull(_Player)) { if (_Type > 0) { _Player.Dossard_Color = <1., 0., 0.>; } else { _Player.Dossard_Color = <1., 1., 1.>; } switch (_Type) { case C_Malus_Reset: { SetPlayerVehicle_ControlledByMode(_Player, False); SetPlayerVehicle_ResetControlledModeValues(_Player); SetPlayer_Delayed_Reset(_Player); } case C_Malus_ForceEngine: { SetPlayer_Delayed_ForceEngine(_Player,True); } case C_Malus_NoEngine: { SetPlayer_Delayed_NoEngine(_Player,True); } case C_Malus_BackwardOnly: { SetPlayer_Delayed_Cruise(_Player,True,-999.); } case C_Malus_NoBrakes: { SetPlayer_Delayed_NoBrakes(_Player,True); } case C_Malus_NoSteer: { SetPlayer_Delayed_NoSteer(_Player,True); } case C_Malus_SlowMotion: { SetPlayer_Delayed_SlowMotion(_Player,True); } case C_Malus_BoostDown: { SetPlayer_Delayed_BoostDown(_Player,True); } case C_Malus_BoostUp: { SetPlayer_Delayed_BoostUp(_Player,True); } case C_Malus_Boost2Down: { SetPlayer_Delayed_Boost2Down(_Player,True); } case C_Malus_Boost2Up: { SetPlayer_Delayed_Boost2Up(_Player,True); } case C_Malus_LockPlayer: { SetPlayerVehicle_ControlledByMode(_Player, True); } case C_Malus_AccelCoef25: { SetPlayer_Delayed_AccelCoef(_Player,0.25); } case C_Malus_AdherenceCoef25: { SetPlayer_Delayed_AdherenceCoef(_Player,0.25); } case C_Malus_ControlCoef25: { SetPlayer_Delayed_ControlCoef(_Player,0.25); } case C_Malus_GravityCoef25: { SetPlayer_Delayed_GravityCoef(_Player,0.25); } // The goal is to kill all Players case C_Malus_Nightmare: { SetPlayerVehicle_ControlledByMode(_Player, True); SetPlayerVehicle_TargetSpeedValue(_Player, ML::Rand(-500.,500.)); SetPlayerVehicle_SteerValue(_Player,ML::Rand(-1.,1.)); SetPlayer_Delayed_Boost2Up(_Player,True); SetPlayer_Delayed_AdherenceCoef(_Player,0.1); } } return True; } return False; } /** Set the UI * * @param _Player Malus Index */ Void SetML() { declare Real TotalWidth = 44.5; declare Text MLText = """