#Extends "Modes/TrackMania/TM_TimeAttack_Online.Script.txt" #Setting S_AdminPlayers "" as "Comma separated admin to manage all players physics" #Setting S_LinksSpectatorsToPlayers "" as "Comma separated Spectator login linked to a Player login with a colon" // Exemple : "Spectator1Login:Player1Login,Spectator1Login:Player2Login,Spectator2Login:any" #Setting S_ForcePlayersToBeControledBySpectators False as "Force Players to be controlled by Spectators" #Setting S_AllowPlayersToBeControledBySpectators True as "Allow Players to be controlled by Spectators" #Struct K_PlayerPhysics { Real AccelCoef; Real AdherenceCoef; Boolean Boost2Down; Boolean Boost2Up; Boolean BoostDown; Boolean BoostUp; Real ControlCoef; Real Cruise; Boolean ForceEngine; Boolean Fragile; Real GravityCoef; Boolean NoBrakes; Boolean NoEngine; Boolean NoSteer; Boolean SlowMotion; } ***Match_StartServer*** *** Race::SetupRecord( MenuConsts::C_ScopeType_Season, MenuConsts::C_ScopeType_PersonalBest, MenuConsts::C_GameMode_TimeAttack, "", False, //C_UploadRecord False, //C_DisplayRecordGhost False, //C_DisplayRecordMedal False, //C_CelebrateRecordGhost False //C_CelebrateRecordMedal ); UIModules_Record::SetSpecialVisibility(False); *** ***Match_AfterLoadHud*** *** ---PhysicsController_UI--- *** ***PhysicsController_UI*** *** SetML(); *** ***Match_InitMap*** *** declare K_PlayerPhysics AllPlayersPhysics = InitPlayerPhysicsVariable(); declare Text Last_AdminPlayers; declare Text Last_LinksSpectatorsToPlayers; declare Text[][Text] Array_LinksSpectatorsToPlayers ; declare Boolean[CSmPlayer] ApplyPhysicsAtRespawn_Queue; // [CSmPlayer => ResetBeforeAppling] declare netwrite Text Net_ScriptEnvironment for Teams[0] = S_ScriptEnvironment; declare netwrite Boolean Net_ServerForcePlayersToBeControledBySpectators for Teams[0] = S_ForcePlayersToBeControledBySpectators; declare netwrite Boolean Net_ServerAllowPlayersToBeControledBySpectators for Teams[0] = S_AllowPlayersToBeControledBySpectators; *** ***Match_PlayLoop*** *** foreach (Event in PendingEvents) { Log::Log("[PendingEvents] Event.Type: " ^ Event.Type); if (Event.Type == CSmModeEvent::EType::OnPlayerAdded) { if (Event.Player != Null) { declare netwrite Boolean Net_PlayerIsAdmin for Event.Player = False; Net_PlayerIsAdmin = TL::Split(",", S_AdminPlayers).exists(Event.Player.User.Login); declare netwrite Text[] Net_CanControlPlayers for Event.Player = ["any"]; if (Array_LinksSpectatorsToPlayers.count == 0) { Net_CanControlPlayers = ["any"]; } else { if (Array_LinksSpectatorsToPlayers.existskey(Event.Player.User.Login)) { Net_CanControlPlayers = Array_LinksSpectatorsToPlayers[Event.Player.User.Login]; } else { Net_CanControlPlayers = []; } } declare netwrite K_PlayerPhysics Net_PlayerPhysics for Event.Player = InitPlayerPhysicsVariable(); Net_PlayerPhysics = AllPlayersPhysics; } } } // Manage Custom UI Events foreach (Event in UIManager.PendingEvents) { // TODO: Add Secure token to admins Log::Log("[UIManager] Event.CustomEventType: " ^ Event.CustomEventType); if (TL::StartsWith("Request.PlayerPhysics.", Event.CustomEventType)) { declare Text EventName = TL::Split(".", Event.CustomEventType)[2]; declare Text Target = Event.CustomEventData[0]; declare Text Value = Event.CustomEventData[1]; if (Target == "all") { AllPlayersPhysics = GetUpdatedPlayerPhysicsVariable(EventName, Value, AllPlayersPhysics); foreach (Player in AllPlayers) { SetPhysicsChange(EventName, Value, Player); declare netwrite K_PlayerPhysics Net_PlayerPhysics for Player = InitPlayerPhysicsVariable(); Net_PlayerPhysics = AllPlayersPhysics; } } else { declare CSmPlayer Player = GetPlayer(Target); if (Player != Null && (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned || Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawning)) { SetPhysicsChange(EventName, Value, Player); declare netwrite K_PlayerPhysics Net_PlayerPhysics for Player = InitPlayerPhysicsVariable(); Net_PlayerPhysics = GetUpdatedPlayerPhysicsVariable(EventName, Value, Net_PlayerPhysics); } } } else if (Event.CustomEventType == "Request.ControlBySpectators") { declare Text Target = Event.CustomEventData[0]; declare Text Value = Event.CustomEventData[1]; declare Boolean Allowed = (Value == "1"); declare CSmPlayer Player = GetPlayer(Target); if (Player != Null) { declare netwrite Boolean Net_PlayerAllowToBeControledBySpectators for Player = True; Net_PlayerAllowToBeControledBySpectators = Allowed; } } else if (Event.CustomEventType == "Request.AdminLock") { declare Boolean Locked = (Event.CustomEventData[1] == "1"); declare netwrite Boolean Net_ControledByAdmins for Teams[0]; Net_ControledByAdmins = Locked; AllPlayersPhysics = InitPlayerPhysicsVariable(); if (Locked) { foreach (Player in AllPlayers) { SetPhysicsChange("Reset", "", Player); declare netwrite K_PlayerPhysics Net_PlayerPhysics for Player = InitPlayerPhysicsVariable(); Net_PlayerPhysics = AllPlayersPhysics; } } } } foreach (Event in RacePendingEvents) { Log::Log("[RacePendingEvents] Event.Type: " ^ Event.Type); if (Event.Type == Events::C_Type_StartLine || Event.Type == Events::C_Type_GiveUp || Event.Type == Events::C_Type_SkipOutro) { ApplyPhysicsAtRespawn_Queue[Event.Player] = False; } else if (Event.Type == Events::C_Type_Respawn) { declare netwrite K_PlayerPhysics Net_PlayerPhysics for Event.Player = InitPlayerPhysicsVariable(); ApplyPhysicsAtRespawn_Queue[Event.Player] = True; } } if (Now % 100 == 0 && ApplyPhysicsAtRespawn_Queue.count > 0) { foreach (Player => HaveToReset in ApplyPhysicsAtRespawn_Queue) { Log::Log("[ApplyPhysicsAtRespawn] Player: " ^ Player.User.Name ^ " / Player.SpawnStatus: " ^ Player.SpawnStatus); if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned || Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawning) { if (HaveToReset) SetPhysicsChange("Reset", "", Player); ApplyPhysicsAtRespawn(Player); ApplyPhysicsAtRespawn_Queue.removekey(Player); } } } if (Last_AdminPlayers != S_AdminPlayers) { Last_AdminPlayers = S_AdminPlayers; declare Text[] Admin = TL::Split(",", S_AdminPlayers); foreach (Player in AllPlayers) { declare netwrite Boolean Net_PlayerIsAdmin for Player = False; Net_PlayerIsAdmin = Admin.exists(Player.User.Login); } } if (Last_LinksSpectatorsToPlayers != S_LinksSpectatorsToPlayers) { Last_LinksSpectatorsToPlayers = S_LinksSpectatorsToPlayers; declare Text[][Text] TemporaryData; if (Last_LinksSpectatorsToPlayers != "") { Log::Log("[Last_LinksSpectatorsToPlayers] Last_LinksSpectatorsToPlayers not empty"); declare Text[] AllLinks= TL::Split(",", Last_LinksSpectatorsToPlayers); foreach (Link in AllLinks) { declare Text[] LinkArray = TL::Split(":", Link); if (LinkArray.count > 1) { log("[Last_LinksSpectatorsToPlayers] LinkArray.count > 1"); if (!TemporaryData.existskey(LinkArray[0])) TemporaryData[LinkArray[0]] = []; TemporaryData[LinkArray[0]].add(LinkArray[1]); } } } Array_LinksSpectatorsToPlayers = TemporaryData; Log::Log("[Last_LinksSpectatorsToPlayers] Array_LinksSpectatorsToPlayers = " ^ Array_LinksSpectatorsToPlayers); foreach (Player in AllPlayers) { if (Player == Null) continue; Log::Log("[Last_LinksSpectatorsToPlayers] Player : " ^ Player.User.Name); declare netwrite Text[] Net_CanControlPlayers for Player = ["any"]; declare netwrite Integer Net_Serial_CanControlPlayers for Player = 0; Net_Serial_CanControlPlayers = Net_Serial_CanControlPlayers + 1; if (Array_LinksSpectatorsToPlayers.count == 0) { Log::Log("[Last_LinksSpectatorsToPlayers] Array_LinksSpectatorsToPlayers.count == 0"); Net_CanControlPlayers = ["any"]; } else { Log::Log("[Last_LinksSpectatorsToPlayers] Array_LinksSpectatorsToPlayers.count != 0"); if (Array_LinksSpectatorsToPlayers.existskey(Player.User.Login)) { Net_CanControlPlayers = Array_LinksSpectatorsToPlayers[Player.User.Login]; } else { Net_CanControlPlayers = []; } Log::Log("[Last_LinksSpectatorsToPlayers] Net_CanControlPlayers : " ^ Net_CanControlPlayers); } } } if (Net_ScriptEnvironment != S_ScriptEnvironment) { Net_ScriptEnvironment = S_ScriptEnvironment; } if (Net_ServerForcePlayersToBeControledBySpectators != S_ForcePlayersToBeControledBySpectators) { Net_ServerForcePlayersToBeControledBySpectators = S_ForcePlayersToBeControledBySpectators; } if (Net_ServerAllowPlayersToBeControledBySpectators != S_AllowPlayersToBeControledBySpectators) { Net_ServerAllowPlayersToBeControledBySpectators = S_AllowPlayersToBeControledBySpectators; } *** K_PlayerPhysics InitPlayerPhysicsVariable() { return K_PlayerPhysics { AccelCoef = 1., AdherenceCoef = 1., ControlCoef = 1., Cruise = 0., GravityCoef = 1. }; } Boolean SetPhysicsChange(Text _EventName, Text _EventValue, CSmPlayer _Player) { Log::Log("[SetPhysicsChange] _EventName: " ^ _EventName ^" / _EventValue: "^ _EventValue ^ " / _Player: " ^ _Player.User.Name); ---PhysicsController_SetPhysicsChange--- if (_Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned || _Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawning) { while (SetPlayer_DelayedIsFull(_Player)) MB_Yield(); switch (_EventName) { case "AccelCoef": SetPlayer_Delayed_AccelCoef(_Player, TL::ToReal(_EventValue)); case "AdherenceCoef": SetPlayer_Delayed_AdherenceCoef(_Player, TL::ToReal(_EventValue)); case "Boost2Down": SetPlayer_Delayed_Boost2Down(_Player, (_EventValue == "1")); case "Boost2Up": SetPlayer_Delayed_Boost2Up(_Player, (_EventValue == "1")); case "BoostDown": SetPlayer_Delayed_BoostDown(_Player, (_EventValue == "1")); case "BoostUp": SetPlayer_Delayed_BoostUp(_Player, (_EventValue == "1")); case "ControlCoef": SetPlayer_Delayed_ControlCoef(_Player, TL::ToReal(_EventValue)); case "Cruise": SetPlayer_Delayed_Cruise(_Player, (_EventValue != "0"), TL::ToReal(_EventValue)); case "ForceEngine": SetPlayer_Delayed_ForceEngine(_Player, (_EventValue == "1")); case "Fragile": SetPlayer_Delayed_Fragile(_Player, (_EventValue == "1")); case "GravityCoef": SetPlayer_Delayed_GravityCoef(_Player, TL::ToReal(_EventValue)); case "NoBrakes": SetPlayer_Delayed_NoBrakes(_Player, (_EventValue == "1")); case "NoEngine": SetPlayer_Delayed_NoEngine(_Player, (_EventValue == "1")); case "NoSteer": SetPlayer_Delayed_NoSteer(_Player, (_EventValue == "1")); case "Reset": SetPlayer_Delayed_Reset(_Player); case "SlowMotion": SetPlayer_Delayed_SlowMotion(_Player, (_EventValue == "1")); } return True; } return False; } K_PlayerPhysics GetUpdatedPlayerPhysicsVariable(Text _EventName, Text _EventValue, K_PlayerPhysics _PlayerPhysics) { declare K_PlayerPhysics NewPlayerPhysics = _PlayerPhysics; switch (_EventName) { case "AccelCoef": NewPlayerPhysics.AccelCoef = TL::ToReal(_EventValue); case "AdherenceCoef": NewPlayerPhysics.AdherenceCoef = TL::ToReal(_EventValue); case "Boost2Down": { NewPlayerPhysics.Boost2Down = (_EventValue == "1"); NewPlayerPhysics.Boost2Up = False; NewPlayerPhysics.BoostDown = False; NewPlayerPhysics.BoostUp = False; } case "Boost2Up": { NewPlayerPhysics.Boost2Down = False; NewPlayerPhysics.Boost2Up = (_EventValue == "1"); NewPlayerPhysics.BoostDown = False; NewPlayerPhysics.BoostUp = False; } case "BoostDown": { NewPlayerPhysics.Boost2Down = False; NewPlayerPhysics.Boost2Up = False; NewPlayerPhysics.BoostDown = (_EventValue == "1"); NewPlayerPhysics.BoostUp = False; } case "BoostUp": { NewPlayerPhysics.Boost2Down = False; NewPlayerPhysics.Boost2Up = False; NewPlayerPhysics.BoostDown = False; NewPlayerPhysics.BoostUp = (_EventValue == "1"); } case "ControlCoef": NewPlayerPhysics.ControlCoef = TL::ToReal(_EventValue); case "Cruise": NewPlayerPhysics.Cruise = TL::ToReal(_EventValue); case "ForceEngine": { NewPlayerPhysics.NoEngine = False; NewPlayerPhysics.ForceEngine = (_EventValue == "1"); } case "Fragile": NewPlayerPhysics.Fragile = (_EventValue == "1"); case "GravityCoef": NewPlayerPhysics.GravityCoef = TL::ToReal(_EventValue); case "NoBrakes": NewPlayerPhysics.NoBrakes = (_EventValue == "1"); case "NoEngine": { NewPlayerPhysics.NoEngine = (_EventValue == "1"); NewPlayerPhysics.ForceEngine = False; } case "NoSteer": NewPlayerPhysics.NoSteer = (_EventValue == "1"); case "Reset": NewPlayerPhysics = InitPlayerPhysicsVariable(); case "SlowMotion": NewPlayerPhysics.SlowMotion = (_EventValue == "1"); } return NewPlayerPhysics; } Void ApplyPhysicsAtRespawn(CSmPlayer _Player) { declare K_PlayerPhysics DefaultPlayerPhysics = InitPlayerPhysicsVariable(); declare netwrite K_PlayerPhysics Net_PlayerPhysics for _Player = InitPlayerPhysicsVariable(); if (DefaultPlayerPhysics.AccelCoef != Net_PlayerPhysics.AccelCoef) SetPhysicsChange("AccelCoef", TL::ToText(Net_PlayerPhysics.AccelCoef), _Player); if (DefaultPlayerPhysics.AdherenceCoef != Net_PlayerPhysics.AdherenceCoef) SetPhysicsChange("AdherenceCoef", TL::ToText(Net_PlayerPhysics.AdherenceCoef), _Player); if (DefaultPlayerPhysics.Boost2Down != Net_PlayerPhysics.Boost2Down) SetPhysicsChange("Boost2Down", "1", _Player); if (DefaultPlayerPhysics.Boost2Up != Net_PlayerPhysics.Boost2Up) SetPhysicsChange("Boost2Up", "1", _Player); if (DefaultPlayerPhysics.BoostDown != Net_PlayerPhysics.BoostDown) SetPhysicsChange("BoostDown", "1", _Player); if (DefaultPlayerPhysics.BoostUp != Net_PlayerPhysics.BoostUp) SetPhysicsChange("BoostUp", "1", _Player); if (DefaultPlayerPhysics.ControlCoef != Net_PlayerPhysics.ControlCoef) SetPhysicsChange("ControlCoef", TL::ToText(Net_PlayerPhysics.ControlCoef), _Player); if (DefaultPlayerPhysics.Cruise != Net_PlayerPhysics.Cruise) SetPhysicsChange("Cruise", TL::ToText(Net_PlayerPhysics.Cruise), _Player); if (DefaultPlayerPhysics.ForceEngine != Net_PlayerPhysics.ForceEngine) SetPhysicsChange("ForceEngine", "1", _Player); if (DefaultPlayerPhysics.Fragile != Net_PlayerPhysics.Fragile) SetPhysicsChange("Fragile", "1", _Player); if (DefaultPlayerPhysics.GravityCoef != Net_PlayerPhysics.GravityCoef) SetPhysicsChange("GravityCoef", TL::ToText(Net_PlayerPhysics.GravityCoef), _Player); if (DefaultPlayerPhysics.NoBrakes != Net_PlayerPhysics.NoBrakes) SetPhysicsChange("NoBrakes", "1", _Player); if (DefaultPlayerPhysics.NoEngine != Net_PlayerPhysics.NoEngine) SetPhysicsChange("NoEngine", "1", _Player); if (DefaultPlayerPhysics.NoSteer != Net_PlayerPhysics.NoSteer) SetPhysicsChange("NoSteer", "1", _Player); if (DefaultPlayerPhysics.SlowMotion != Net_PlayerPhysics.SlowMotion) SetPhysicsChange("SlowMotion", "1", _Player); log("[ApplyPhysicsAtRespawn] Now After SetPhysics: " ^ Now); } Void SetML() { declare Text MLText = """