#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". To prevent all spectators to control all players, just add a random key:value like "lock:lock" #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; } #Struct K_NewPhysicsEffect { Integer Time; Text Effect; Text Value; } ***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 K_NewPhysicsEffect[][Text] ApplyPhysics_Queue; 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_InitPlayLoop*** *** ApplyPhysics_Queue = []; *** ***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) { if (!ApplyPhysics_Queue.existskey(Player.User.WebServicesUserId) || EventName == "Reset") { ApplyPhysics_Queue[Player.User.WebServicesUserId] = []; } else { foreach (Key => NewPhysicsEffects in ApplyPhysics_Queue[Player.User.WebServicesUserId]) { if (NewPhysicsEffects.Effect == EventName && NewPhysicsEffects.Value == Value && NewPhysicsEffects.Time == Now) { ApplyPhysics_Queue[Player.User.WebServicesUserId].removekey(Key); } } } ---PhysicsController_BeforeAddApplyPhysicsEffect--- ApplyPhysics_Queue[Player.User.WebServicesUserId].add(GetPhysics(EventName, Value, Now)); 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)) { if (!ApplyPhysics_Queue.existskey(Player.User.WebServicesUserId) || EventName == "Reset") { ApplyPhysics_Queue[Player.User.WebServicesUserId] = []; } else { declare Boolean AlreadyPassed; foreach (Key => NewPhysicsEffects in ApplyPhysics_Queue[Player.User.WebServicesUserId]) { if (NewPhysicsEffects.Effect == EventName && NewPhysicsEffects.Value == Value && NewPhysicsEffects.Time == Now) { ApplyPhysics_Queue[Player.User.WebServicesUserId].removekey(Key); } } } ---PhysicsController_BeforeAddApplyPhysicsEffect--- ApplyPhysics_Queue[Player.User.WebServicesUserId].add(GetPhysics(EventName, Value, Now)); 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) { ApplyPhysics_Queue[Player.User.WebServicesUserId] = []; ApplyPhysics_Queue[Player.User.WebServicesUserId].add(K_NewPhysicsEffect { Time = Now, Effect = "Reset" }); declare netwrite K_PlayerPhysics Net_PlayerPhysics for Player = InitPlayerPhysicsVariable(); Net_PlayerPhysics = AllPlayersPhysics; } } } ---PhysicsController_NewCustomEvents--- } ---PhysicsController_ApplyPhysics--- ---PhysicsController_PhysicsAtRespawn--- 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::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; } *** ***PhysicsController_ApplyPhysics*** *** foreach (AccountId => ArrayNewPlayerPhysics in ApplyPhysics_Queue) { declare CSmPlayer Player = ModeUtils::GetPlayerFromAccountId(AccountId); foreach (Key => NewPlayerPhysics in ArrayNewPlayerPhysics) { if (Player != Null && NewPlayerPhysics.Time <= Now && (Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned || Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawning)) { Log::Log("[ApplyPhysics] Trying to set Event " ^ NewPlayerPhysics.Effect ^ " for " ^ Player.User.Name); if (SetPlayerPhysics(NewPlayerPhysics.Effect, NewPlayerPhysics.Value, Player)) { ApplyPhysics_Queue[AccountId].removekey(Key); if (ApplyPhysics_Queue[AccountId].count == 0) ApplyPhysics_Queue.removekey(AccountId); } } else if (NewPlayerPhysics.Time + 10000 < Now) { // Clear old entry ApplyPhysics_Queue[AccountId].removekey(Key); if (ApplyPhysics_Queue[AccountId].count == 0) ApplyPhysics_Queue.removekey(AccountId); } } } *** ***PhysicsController_PhysicsAtRespawn*** *** 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 || Event.Type == Events::C_Type_Respawn) { declare K_NewPhysicsEffect[] NewPhysics = GetDiffPhysics(Event.Player, (Event.Type == Events::C_Type_Respawn)); if (NewPhysics.count > 0) { ApplyPhysics_Queue[Event.Player.User.WebServicesUserId] = NewPhysics; } } } *** K_PlayerPhysics InitPlayerPhysicsVariable() { return K_PlayerPhysics { AccelCoef = 1., AdherenceCoef = 1., ControlCoef = 1., Cruise = 0., GravityCoef = 1. }; } Boolean SetPlayerPhysics(Text _EventName, Text _EventValue, CSmPlayer _Player) { Log::Log("[SetPhysicsChange] _EventName: " ^ _EventName ^" / _EventValue: "^ _EventValue ^ " / _Player: " ^ _Player.User.Name); ---PhysicsController_SetPhysicsChange--- if (_Player != Null && !SetPlayer_DelayedIsFull(_Player) && (_Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawned || _Player.SpawnStatus == CSmPlayer::ESpawnStatus::Spawning)) { 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; } K_NewPhysicsEffect GetPhysics(Text _Effect, Text _Value, Integer _Time) { return K_NewPhysicsEffect { Time = _Time, Effect = _Effect, Value = _Value }; } K_NewPhysicsEffect[] GetDiffPhysics(CSmPlayer _Player, Boolean _Reset) { declare K_PlayerPhysics DefaultPlayerPhysics = InitPlayerPhysicsVariable(); declare netwrite K_PlayerPhysics Net_PlayerPhysics for _Player = InitPlayerPhysicsVariable(); declare K_NewPhysicsEffect[] output; if (_Reset) output.add(K_NewPhysicsEffect { Time = Now, Effect = "Reset" }); if (DefaultPlayerPhysics.AccelCoef != Net_PlayerPhysics.AccelCoef) output.add(K_NewPhysicsEffect { Time = Now, Effect = "AccelCoef", Value = TL::ToText(Net_PlayerPhysics.AccelCoef)}); if (DefaultPlayerPhysics.AdherenceCoef != Net_PlayerPhysics.AdherenceCoef) output.add(K_NewPhysicsEffect { Time = Now, Effect = "AdherenceCoef", Value = TL::ToText(Net_PlayerPhysics.AdherenceCoef)}); if (DefaultPlayerPhysics.Boost2Down != Net_PlayerPhysics.Boost2Down) output.add(K_NewPhysicsEffect { Time = Now, Effect = "Boost2Down", Value = "1"}); if (DefaultPlayerPhysics.Boost2Up != Net_PlayerPhysics.Boost2Up) output.add(K_NewPhysicsEffect { Time = Now, Effect = "Boost2Up", Value = "1"}); if (DefaultPlayerPhysics.BoostDown != Net_PlayerPhysics.BoostDown) output.add(K_NewPhysicsEffect { Time = Now, Effect = "BoostDown", Value = "1"}); if (DefaultPlayerPhysics.BoostUp != Net_PlayerPhysics.BoostUp) output.add(K_NewPhysicsEffect { Time = Now, Effect = "BoostUp", Value = "1"}); if (DefaultPlayerPhysics.ControlCoef != Net_PlayerPhysics.ControlCoef) output.add(K_NewPhysicsEffect { Time = Now, Effect = "ControlCoef", Value = TL::ToText(Net_PlayerPhysics.ControlCoef)}); if (DefaultPlayerPhysics.Cruise != Net_PlayerPhysics.Cruise) output.add(K_NewPhysicsEffect { Time = Now, Effect = "Cruise", Value = TL::ToText(Net_PlayerPhysics.Cruise)}); if (DefaultPlayerPhysics.ForceEngine != Net_PlayerPhysics.ForceEngine) output.add(K_NewPhysicsEffect { Time = Now, Effect = "ForceEngine", Value = "1"}); if (DefaultPlayerPhysics.Fragile != Net_PlayerPhysics.Fragile) output.add(K_NewPhysicsEffect { Time = Now, Effect = "Fragile", Value = "1"}); if (DefaultPlayerPhysics.GravityCoef != Net_PlayerPhysics.GravityCoef) output.add(K_NewPhysicsEffect { Time = Now, Effect = "GravityCoef", Value = TL::ToText(Net_PlayerPhysics.GravityCoef)}); if (DefaultPlayerPhysics.NoBrakes != Net_PlayerPhysics.NoBrakes) output.add(K_NewPhysicsEffect { Time = Now, Effect = "NoBrakes", Value = "1"}); if (DefaultPlayerPhysics.NoEngine != Net_PlayerPhysics.NoEngine) output.add(K_NewPhysicsEffect { Time = Now, Effect = "NoEngine", Value = "1"}); if (DefaultPlayerPhysics.NoSteer != Net_PlayerPhysics.NoSteer) output.add(K_NewPhysicsEffect { Time = Now, Effect = "NoSteer", Value = "1"}); if (DefaultPlayerPhysics.SlowMotion != Net_PlayerPhysics.SlowMotion) output.add(K_NewPhysicsEffect { Time = Now, Effect = "SlowMotion", Value = "1"}); return output; } Void SetML() { declare Text MLText = """