TM2020-Gamemodes/PhysicsController.Script.txt

758 lines
40 KiB
Plaintext

#Extends "Modes/TrackMania/TM_TimeAttack_Online.Script.txt"
#Setting S_AdminPlayers "" as "Comma separated admin to manage all players physics"
#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***
***
SetML();
***
***Match_InitMap***
***
declare K_PlayerPhysics AllPlayersPhysics = InitPlayerPhysicsVariable();
declare Text Last_AdminPlayers;
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 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 (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);
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 = """
<manialink name="PhysicsDiscovery_UI" version="3">
<script><!--
#Include "TextLib" as TL
#Include "MathLib" as ML
#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;
}
Void DevLog(Text _LogText) {
declare netread Text Net_ScriptEnvironment for Teams[0] = "production";
if (Net_ScriptEnvironment == "development") log(_LogText);
}
Void Sleep(Integer _Duration) {
declare EndTime = Now + _Duration;
while (Now < EndTime) {
yield;
}
}
Text BooleanToText(Boolean _Value) {
if (_Value) return "1";
return "0";
}
Boolean InputPlayerIsSpectator() {
if (GUIPlayer != Null && InputPlayer != Null && GUIPlayer.User.Login == InputPlayer.User.Login) return False;
return True;
}
Void ToggleUI() {
declare CMlFrame Frame_Global <=> (Page.GetFirstChild("frame-global") as CMlFrame);
declare CMlFrame Frame_UI <=> (Page.GetFirstChild("frame-UI") as CMlFrame);
declare CMlQuad Quad_Toggle <=> (Page.GetFirstChild("Toggle_SettingButton") as CMlQuad);
AnimMgr.Flush(Frame_Global);
AnimMgr.Flush(Frame_UI);
declare Real GlobalEndPosX;
if (Frame_UI.Visible) {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_RIGHT_OBLIQUE.dds");
Quad_Toggle.RelativePosition_V3.X = 5.;
GlobalEndPosX = -220.;
AnimMgr.Add(Frame_UI, "<frame hidden=\"1\" />", Now, 250, CAnimManager::EAnimManagerEasing::Linear);
} else {
Quad_Toggle.ChangeImageUrl("file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds");
Quad_Toggle.RelativePosition_V3.X = 0.;
GlobalEndPosX = -160.;
Frame_UI.Visible = True;
}
AnimMgr.Add(Frame_Global, "<frame pos=\"" ^GlobalEndPosX^" "^Frame_Global.RelativePosition_V3.Y^ "\" />", Now, 250, CAnimManager::EAnimManagerEasing::Linear);
}
Void UpdateSliderValues() {
declare Slider_Cruise <=> (Page.GetFirstChild("Cruise_Slider") as CMlSlider);
declare Slider_AccelCoef <=> (Page.GetFirstChild("AccelCoef_Slider") as CMlSlider);
declare Slider_AdherenceCoef <=> (Page.GetFirstChild("AdherenceCoef_Slider") as CMlSlider);
declare Slider_ControlCoef <=> (Page.GetFirstChild("ControlCoef_Slider") as CMlSlider);
declare Slider_GravityCoef <=> (Page.GetFirstChild("GravityCoef_Slider") as CMlSlider);
declare Entry_Cruise <=> (Page.GetFirstChild("Cruise_Entry") as CMlEntry);
declare Entry_AccelCoef <=> (Page.GetFirstChild("AccelCoef_Entry") as CMlEntry);
declare Entry_AdherenceCoef <=> (Page.GetFirstChild("AdherenceCoef_Entry") as CMlEntry);
declare Entry_ControlCoef <=> (Page.GetFirstChild("ControlCoef_Entry") as CMlEntry);
declare Entry_GravityCoef <=> (Page.GetFirstChild("GravityCoef_Entry") as CMlEntry);
Entry_Cruise.Value = TL::ToText(ML::FloorInteger(Slider_Cruise.Value));
Entry_AccelCoef.Value = TL::ToText(ML::FloorInteger(Slider_AccelCoef.Value * 100));
Entry_AdherenceCoef.Value = TL::ToText(ML::FloorInteger(Slider_AdherenceCoef.Value * 100));
Entry_ControlCoef.Value = TL::ToText(ML::FloorInteger(Slider_ControlCoef.Value * 100));
Entry_GravityCoef.Value = TL::ToText(ML::FloorInteger(Slider_GravityCoef.Value * 100));
}
Void UpdateUIButtons(K_PlayerPhysics _PlayerPhysics) {
declare Quad_Boost2Down_EffectButton <=> (Page.GetFirstChild("Boost2Down_EffectButton") as CMlQuad);
declare Quad_Boost2Up_EffectButton <=> (Page.GetFirstChild("Boost2Up_EffectButton") as CMlQuad);
declare Quad_BoostDown_EffectButton <=> (Page.GetFirstChild("BoostDown_EffectButton") as CMlQuad);
declare Quad_BoostUp_EffectButton <=> (Page.GetFirstChild("BoostUp_EffectButton") as CMlQuad);
declare Quad_NoBrakes_EffectButton <=> (Page.GetFirstChild("NoBrakes_EffectButton") as CMlQuad);
declare Quad_NoEngine_EffectButton <=> (Page.GetFirstChild("NoEngine_EffectButton") as CMlQuad);
declare Quad_ForceEngine_EffectButton <=> (Page.GetFirstChild("ForceEngine_EffectButton") as CMlQuad);
declare Quad_NoSteer_EffectButton <=> (Page.GetFirstChild("NoSteer_EffectButton") as CMlQuad);
declare Quad_SlowMotion_EffectButton <=> (Page.GetFirstChild("SlowMotion_EffectButton") as CMlQuad);
declare Quad_Fragile_EffectButton <=> (Page.GetFirstChild("Fragile_EffectButton") as CMlQuad);
if (_PlayerPhysics.Boost2Down) Quad_Boost2Down_EffectButton.ModulateColor= <1., 1., 1.>;
else Quad_Boost2Down_EffectButton.ModulateColor= <0., 0., 0.>;
if (_PlayerPhysics.Boost2Up) Quad_Boost2Up_EffectButton.ModulateColor= <1., 1., 1.>;
else Quad_Boost2Up_EffectButton.ModulateColor= <0., 0., 0.>;
if (_PlayerPhysics.BoostDown) Quad_BoostDown_EffectButton.ModulateColor= <1., 1., 1.>;
else Quad_BoostDown_EffectButton.ModulateColor= <0., 0., 0.>;
if (_PlayerPhysics.BoostUp) Quad_BoostUp_EffectButton.ModulateColor= <1., 1., 1.>;
else Quad_BoostUp_EffectButton.ModulateColor= <0., 0., 0.>;
if (_PlayerPhysics.ForceEngine) Quad_ForceEngine_EffectButton.ModulateColor= <1., 1., 1.>;
else Quad_ForceEngine_EffectButton.ModulateColor= <0., 0., 0.>;
if (_PlayerPhysics.Fragile) Quad_Fragile_EffectButton.ModulateColor= <1., 1., 1.>;
else Quad_Fragile_EffectButton.ModulateColor= <0., 0., 0.>;
if (_PlayerPhysics.NoBrakes) Quad_NoBrakes_EffectButton.ModulateColor= <1., 1., 1.>;
else Quad_NoBrakes_EffectButton.ModulateColor= <0., 0., 0.>;
if (_PlayerPhysics.NoEngine) Quad_NoEngine_EffectButton.ModulateColor= <1., 1., 1.>;
else Quad_NoEngine_EffectButton.ModulateColor= <0., 0., 0.>;
if (_PlayerPhysics.NoSteer) Quad_NoSteer_EffectButton.ModulateColor= <1., 1., 1.>;
else Quad_NoSteer_EffectButton.ModulateColor= <0., 0., 0.>;
if (_PlayerPhysics.SlowMotion) Quad_SlowMotion_EffectButton.ModulateColor= <1., 1., 1.>;
else Quad_SlowMotion_EffectButton.ModulateColor= <0., 0., 0.>;
}
Void UpdateUISlider(K_PlayerPhysics _PlayerPhysics) {
declare Slider_Cruise <=> (Page.GetFirstChild("Cruise_Slider") as CMlSlider);
declare Slider_AccelCoef <=> (Page.GetFirstChild("AccelCoef_Slider") as CMlSlider);
declare Slider_AdherenceCoef <=> (Page.GetFirstChild("AdherenceCoef_Slider") as CMlSlider);
declare Slider_ControlCoef <=> (Page.GetFirstChild("ControlCoef_Slider") as CMlSlider);
declare Slider_GravityCoef <=> (Page.GetFirstChild("GravityCoef_Slider") as CMlSlider);
Slider_Cruise.Value = _PlayerPhysics.Cruise;
Slider_AccelCoef.Value = _PlayerPhysics.AccelCoef;
Slider_AdherenceCoef.Value = _PlayerPhysics.AdherenceCoef;
Slider_ControlCoef.Value = _PlayerPhysics.ControlCoef;
Slider_GravityCoef.Value = _PlayerPhysics.GravityCoef;
yield; // Wait UI changed before load value
UpdateSliderValues();
}
Text GetValueOfAnEffect(K_PlayerPhysics _PlayerPhysics, Text _Effect) {
switch (_Effect) {
case "AccelCoef": return TL::ToText(_PlayerPhysics.AccelCoef);
case "AdherenceCoef": return TL::ToText(_PlayerPhysics.AdherenceCoef);
case "Boost2Down": return BooleanToText(_PlayerPhysics.Boost2Down);
case "Boost2Up": return BooleanToText(_PlayerPhysics.Boost2Up);
case "BoostDown": return BooleanToText(_PlayerPhysics.BoostDown);
case "BoostUp": return BooleanToText(_PlayerPhysics.BoostUp);
case "ControlCoef": return TL::ToText(_PlayerPhysics.ControlCoef);
case "Cruise": return TL::ToText(ML::FloorInteger(_PlayerPhysics.Cruise));
case "ForceEngine": return BooleanToText(_PlayerPhysics.ForceEngine);
case "Fragile": return BooleanToText(_PlayerPhysics.Fragile);
case "GravityCoef": return TL::ToText(_PlayerPhysics.GravityCoef);
case "NoBrakes": return BooleanToText(_PlayerPhysics.NoBrakes);
case "NoEngine": return BooleanToText(_PlayerPhysics.NoEngine);
case "NoSteer": return BooleanToText(_PlayerPhysics.NoSteer);
case "SlowMotion": return BooleanToText(_PlayerPhysics.SlowMotion);
}
return "";
}
K_PlayerPhysics InitPlayerPhysicsVariable() {
return K_PlayerPhysics {
AccelCoef = 1.,
AdherenceCoef = 1.,
ControlCoef = 1.,
Cruise = 0.,
GravityCoef = 1.
};
}
main() {
DevLog("[main] Starting main loop");
declare Frame_Global <=> (Page.GetFirstChild("frame-global") as CMlFrame);
declare Quad_Bg <=> (Page.GetFirstChild("quad-bg") as CMlQuad);
declare Quad_Fg <=> (Page.GetFirstChild("quad-fg") as CMlQuad);
declare Label_Warning <=> (Page.GetFirstChild("label-warning") as CMlLabel);
declare Quad_ControledByAdmins <=> (Page.GetFirstChild("ControledByAdmins_SettingButton") as CMlQuad);
declare Quad_AllowSpectatorsControl <=> (Page.GetFirstChild("AllowSpectatorsControl_SettingButton") as CMlQuad);
declare Frame_AllowSpectatorsControl <=> (Page.GetFirstChild("AllowSpectatorsControl_Frame") as CMlFrame);
declare Frame_Admin <=> (Page.GetFirstChild("Admin_Frame") as CMlFrame);
declare K_PlayerPhysics Last_PlayerPhysics = InitPlayerPhysicsVariable();
declare Boolean Last_UIisVisible = False;
declare netread Boolean Net_ServerForcePlayersToBeControledBySpectators for Teams[0] = False;
declare netread Boolean Net_ServerAllowPlayersToBeControledBySpectators for Teams[0] = True;
declare netread Boolean Net_ControledByAdmins for Teams[0];
declare Boolean Last_ControledByAdmins = False;
declare Boolean Last_PlayerIsAdmin = False;
declare Boolean Last_PlayerIsSpectator = False;
declare Boolean Last_ServerForcePlayersToBeControledBySpectators = False;
declare Boolean Last_ServerAllowPlayersToBeControledBySpectators = True;
declare Boolean Last_PlayerAllowToBeControledBySpectators = True;
declare Boolean Last_UIAllowToBeControledBySpectators = False;
declare Text Last_SliderValueToSend;
declare Integer Timer_NeedToSendCommandSlider = 0;
declare CSmPlayer Owner;
if (GUIPlayer != Null) Owner <=> GUIPlayer;
else Owner <=> InputPlayer;
UpdateUIButtons(Last_PlayerPhysics);
UpdateUISlider(Last_PlayerPhysics);
while(True) {
yield;
declare CSmPlayer Owner;
if (GUIPlayer != Null) Owner <=> GUIPlayer;
else Owner <=> InputPlayer;
if (Owner != Null) {
declare netread Boolean Net_PlayerAllowToBeControledBySpectators for Owner = True;
declare netread Boolean Net_PlayerIsAdmin for InputPlayer;
declare netread K_PlayerPhysics Net_PlayerPhysics for Owner = InitPlayerPhysicsVariable();
if (Last_ControledByAdmins != Net_ControledByAdmins ||
Last_PlayerIsAdmin != Net_PlayerIsAdmin ||
Last_PlayerIsSpectator != InputPlayerIsSpectator() ||
Last_PlayerAllowToBeControledBySpectators != Net_PlayerAllowToBeControledBySpectators ||
Last_ServerForcePlayersToBeControledBySpectators != Net_ServerForcePlayersToBeControledBySpectators ||
Last_ServerAllowPlayersToBeControledBySpectators != Net_ServerAllowPlayersToBeControledBySpectators
) {
DevLog("[main] Update Last_PlayerIsAdmin to " ^ Net_PlayerIsAdmin);
Last_PlayerIsAdmin = Net_PlayerIsAdmin;
Frame_Admin.Visible = Last_PlayerIsAdmin;
Last_PlayerIsSpectator = InputPlayerIsSpectator();
DevLog("[main] Update Warning page");
Last_ControledByAdmins = Net_ControledByAdmins;
Quad_ControledByAdmins.StyleSelected = Last_ControledByAdmins;
Last_PlayerAllowToBeControledBySpectators = Net_PlayerAllowToBeControledBySpectators;
Quad_AllowSpectatorsControl.StyleSelected = Last_PlayerAllowToBeControledBySpectators;
Last_ServerForcePlayersToBeControledBySpectators = Net_ServerForcePlayersToBeControledBySpectators;
Last_ServerAllowPlayersToBeControledBySpectators = Net_ServerAllowPlayersToBeControledBySpectators;
if (!Net_PlayerIsAdmin && (Last_ControledByAdmins || (Last_ServerForcePlayersToBeControledBySpectators && !Last_PlayerIsSpectator) || (!Last_ServerForcePlayersToBeControledBySpectators && (!Last_ServerAllowPlayersToBeControledBySpectators || !Last_PlayerAllowToBeControledBySpectators) && Last_PlayerIsSpectator))) {
Label_Warning.Visible = True;
Quad_Fg.Visible = True;
if (Last_ControledByAdmins) Label_Warning.Value = "Managed by an Admin";
else if (Last_ServerForcePlayersToBeControledBySpectators) Label_Warning.Value = "Can only be controlled by a spectator";
else if (!Last_ServerAllowPlayersToBeControledBySpectators) Label_Warning.Value = "Can only be controlled by the player";
else Label_Warning.Value = "Player refuses to be managed by a spectator";
} else {
Label_Warning.Visible = False;
Quad_Fg.Visible = False;
}
}
if (Last_UIAllowToBeControledBySpectators && (!Net_ServerAllowPlayersToBeControledBySpectators || Net_ServerForcePlayersToBeControledBySpectators || Last_PlayerIsSpectator)) {
Last_UIAllowToBeControledBySpectators = False;
Quad_Bg.Size = <60., 74.>;
Quad_Fg.Size = <60., 74.>;
Frame_AllowSpectatorsControl.Visible = False;
} else if (!Last_UIAllowToBeControledBySpectators && Net_ServerAllowPlayersToBeControledBySpectators && !Net_ServerForcePlayersToBeControledBySpectators && !Last_PlayerIsSpectator) {
Last_UIAllowToBeControledBySpectators = True;
Quad_Bg.Size = <60., 85.>;
Quad_Fg.Size = <60., 85.>;
Frame_AllowSpectatorsControl.Visible = True;
}
if (Last_PlayerPhysics != Net_PlayerPhysics) {
DevLog("[main] Update Last_PlayerPhysics UI to " ^ Net_PlayerPhysics);
Last_PlayerPhysics = Net_PlayerPhysics;
UpdateUIButtons(Last_PlayerPhysics);
UpdateUISlider(Last_PlayerPhysics);
}
if (!Last_UIisVisible && (GUIPlayer != Null || Last_PlayerIsAdmin || Last_ControledByAdmins)) {
Last_UIisVisible = True;
Frame_Global.Visible = True;
} else if (Last_UIisVisible && GUIPlayer == Null && !Last_PlayerIsAdmin && !Last_ControledByAdmins) {
Last_UIisVisible = False;
Frame_Global.Visible = False;
}
// Events
foreach(Event in PendingEvents) {
DevLog("[PendingEvents] Event.Type: " ^ Event.Type);
if (Owner != Null) {
if (Event.Type == CMlScriptEvent::Type::MouseClick) {
if (TL::Find("_EffectButton", Event.ControlId, True, True) && (GUIPlayer != Null || Last_ControledByAdmins)) {
declare Text Effect = TL::Replace(Event.ControlId, "_EffectButton", "");
declare Text Target;
if (Net_PlayerIsAdmin && Last_ControledByAdmins) Target = "all";
else Target = Owner.User.Login;
DevLog("[PendingEvents] Request of " ^ Effect ^ " to " ^ Target);
if (GetValueOfAnEffect(Last_PlayerPhysics, Effect) == "1") SendCustomEvent("Request.PlayerPhysics." ^ Effect, [Target, "0"]);
else SendCustomEvent("Request.PlayerPhysics." ^ Effect, [Target, "1"]);
} else if (TL::Find("_Slider", Event.ControlId, True, True) && (GUIPlayer != Null || Last_ControledByAdmins)) {
Timer_NeedToSendCommandSlider = Now + 100;
Last_SliderValueToSend = Event.ControlId;
UpdateSliderValues();
} else if (Event.ControlId == "AllowSpectatorsControl_SettingButton" && (GUIPlayer != Null || Last_ControledByAdmins)) {
DevLog("[PendingEvents] Request ControlBySpectators " ^ !Last_PlayerAllowToBeControledBySpectators ^ " to " ^ Owner.User.Login);
if (Last_PlayerAllowToBeControledBySpectators) SendCustomEvent("Request.ControlBySpectators", [Owner.User.Login, "0"]);
else SendCustomEvent("Request.ControlBySpectators", [Owner.User.Login, "1"]);
} else if (Event.ControlId == "ControledByAdmins_SettingButton") {
DevLog("[PendingEvents] Request ControledByAdmins " ^ !Last_ControledByAdmins ^ " by " ^ Owner.User.Login);
if (Last_ControledByAdmins) SendCustomEvent("Request.AdminLock", ["", "0"]); // TODO: Secure with a token
else SendCustomEvent("Request.AdminLock", ["", "1"]);
} else if (Event.ControlId == "Toggle_SettingButton") {
DevLog("[PendingEvents] Toggle UI by " ^ Owner.User.Login);
ToggleUI();
} else {
UpdateUISlider(Last_PlayerPhysics); // To update Slider Right Value
}
} else if (Event.Type == CMlScriptEvent::Type::EntrySubmit && TL::Find("_Entry", Event.ControlId, True, True)) {
declare Text Effect = TL::Replace(Event.ControlId, "_Entry", "");
declare CMlEntry Entry = (Page.GetFirstChild(Event.ControlId) as CMlEntry);
declare CMlSlider Slider = (Page.GetFirstChild(Effect ^ "_Slider") as CMlSlider);
declare Integer Value = TL::ToInteger(Entry.Value);
declare Text Target;
if (Net_PlayerIsAdmin && Last_ControledByAdmins) Target = "all";
else Target = Owner.User.Login;
if (Event.ControlId == "Cruise_Entry") {
if ((Value != -1 && Value > -1000 && Value < 1000) || Entry.Value == "-1") {
DevLog("[PendingEvents] Send Entry value for " ^ Effect ^ " with value " ^ Value ^ " to " ^ Target);
SendCustomEvent("Request.PlayerPhysics." ^ Effect, [Target, TL::ToText(Value)]);
} else {
DevLog("[PendingEvents] Invalid Value for Entry " ^ Effect);
Entry.Value = TL::ToText(ML::FloorInteger(Slider.Value));
}
} else {
if (Value != -1 && Value > 0 && Value <= 100) {
DevLog("[PendingEvents] Send Entry value for " ^ Effect ^ " with value " ^ Value ^ " to " ^ Target);
Slider.Value = ML::ToReal(Value)/100;
SendCustomEvent("Request.PlayerPhysics." ^ Effect, [Target, TL::ToText(ML::ToReal(Value)/100)]);
} else {
DevLog("[PendingEvents] Invalid Value for Entry " ^ Effect);
Entry.Value = TL::ToText(ML::FloorInteger(Slider.Value * 100));
}
}
} else if (Event.Type == CMlScriptEvent::Type::MouseOver && TL::Find("_EffectButton", Event.ControlId, True, True)) {
declare Quad <=> (Page.GetFirstChild(Event.ControlId) as CMlQuad);
Quad.Opacity = 0.7;
} else if (Event.Type == CMlScriptEvent::Type::MouseOut && TL::Find("_EffectButton", Event.ControlId, True, True)) {
declare Quad <=> (Page.GetFirstChild(Event.ControlId) as CMlQuad);
Quad.Opacity = 0.5;
}
} else {
UpdateUISlider(Last_PlayerPhysics); // To update Slider Right Value
}
}
if (Timer_NeedToSendCommandSlider > 0 && Now > Timer_NeedToSendCommandSlider ) {
DevLog("[main] Value of the slider " ^ Last_SliderValueToSend ^ " sent");
Timer_NeedToSendCommandSlider = 0;
declare Slider <=> (Page.GetFirstChild(Last_SliderValueToSend) as CMlSlider);
declare Text Target;
if (Net_PlayerIsAdmin && Last_ControledByAdmins) Target = "all";
else Target = Owner.User.Login;
SendCustomEvent("Request.PlayerPhysics." ^ TL::Replace(Last_SliderValueToSend, "_Slider", ""), [Target, TL::ToText(Slider.Value)]);
}
}
}
}
--></script>
<stylesheet>
<style class="text-label" textfont="GameFontSemiBold" textcolor="ffffff" textsize="1.5" halign="left" valign="center" textprefix="$i$t"/>
<style class="slider" iconidbar="Slider_light" iconidcursor="SliderCursor_light" textcolor="ffffff" valign="center" />
<style class="text-value" textfont="GameFontBlack" textcolor="ffffff" textsize="1.5" halign="right" valign="center" focusareacolor1="00000000" focusareacolor2="00000000"/>
<style class="text-suffix" textfont="GameFontBlack" textcolor="ffffff" textsize="0.7" halign="center" valign="center" textprefix="$i$t"/>
<style class="quad-effects" halign="center" valign="center" />
</stylesheet>
<frame id="frame-global" hidden="1" pos="-160 40">
<frame pos="58 -2.5" id="frame-toggle">
<quad id="Toggle_SettingButton" pos="0 0" size="4 4" class="quad-base" z-index="3" opacity="0.9" scriptevents="1" halign="center" valign="center" image="file://Media/Manialinks/Nadeo/TMNext/Menus/Icons/128x128/ICON_ARROW_LEFT_OBLIQUE.dds" colorize="fff"/>
</frame>
<frame id="frame-UI">
<quad id="quad-bg" pos="0 0" z-index="-1" size="60 74" bgcolor="000" opacity="0.5" /> <!-- 85 for regular, 74 without spec control-->
<quad id="quad-fg" pos="0 0" z-index="5" size="60 74" bgcolor="000" opacity="0.5" hidden="1" scriptevents="1" />
<label id="label-warning" class="text-suffix" hidden="1" z-index="6" pos="30 -35" textsize="2.5" size="50 10" autonewline="1"/>
<label class="text-suffix" pos="30 -5" textsize="3" z-index="0" size="50 10" text="Physics Control"/>
<frame pos="0.5 -6">
<frame pos="30 0">
<quad class="quad-effects" pos="-3 -7.5" z-index="1" size="3 3'" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/Reset.dds" />
<label class="text-suffix" pos="3 -7.2" z-index="0" size="20 5" text="RESET"/>
<quad id="Reset_EffectButton" class="quad-effects" pos="0 -7.5" z-index="0" size="52.5 5" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
<frame pos="0 -9">
<frame pos="0 0">
<quad class="quad-effects" pos="7.5 -7.5" z-index="1" size="7 7" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/ReactorBoost.dds" rot="180" />
<quad id="BoostUp_EffectButton" class="quad-effects" pos="7.5 -7.5" z-index="0" size="9 9" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
<frame pos="11 0">
<quad class="quad-effects" pos="7.5 -7.5" z-index="1" size="7 7" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/ReactorBoost2.dds" rot="180" />
<quad id="Boost2Up_EffectButton" class="quad-effects" pos="7.5 -7.5" z-index="0" size="9 9" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
<frame pos="22 0">
<quad class="quad-effects" pos="7.5 -7.5" z-index="1" size="7 7" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/NoBrakes.dds" />
<quad id="NoBrakes_EffectButton" class="quad-effects" pos="7.5 -7.5" z-index="0" size="9 9" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
<frame pos="33 0">
<quad class="quad-effects" pos="7.5 -7.5" z-index="1" size="7 7" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/FreeWheeling.dds" />
<quad id="NoEngine_EffectButton" class="quad-effects" pos="7.5 -7.5" z-index="0" size="9 9" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
<frame pos="44 0">
<quad class="quad-effects" pos="7.5 -7.5" z-index="1" size="7 7" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/ForceAcceleration.dds"/>
<quad id="ForceEngine_EffectButton" class="quad-effects" pos="7.5 -7.5" z-index="0" size="9 9" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
</frame>
<frame pos="0 -20">
<frame pos="0 0">
<quad class="quad-effects" pos="7.5 -7.5" z-index="1" size="7 7" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/ReactorBoost.dds"/>
<quad id="BoostDown_EffectButton" class="quad-effects" pos="7.5 -7.5" z-index="0" size="9 9" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
<frame pos="11 0">
<quad class="quad-effects" pos="7.5 -7.5" z-index="1" size="7 7" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/ReactorBoost2.dds" />
<quad id="Boost2Down_EffectButton" class="quad-effects" pos="7.5 -7.5" z-index="0" size="9 9" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
<frame pos="22 0">
<quad class="quad-effects" pos="7.5 -7.5" z-index="1" size="7 7" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/NoSteering.dds" />
<quad id="NoSteer_EffectButton" class="quad-effects" pos="7.5 -7.5" z-index="0" size="9 9" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
<frame pos="33 0">
<quad class="quad-effects" pos="7.5 -7.5" z-index="1" size="7 7" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/SlowMotion.dds" />
<quad id="SlowMotion_EffectButton" class="quad-effects" pos="7.5 -7.5" z-index="0" size="9 9" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
<frame pos="44 0">
<quad class="quad-effects" pos="7.5 -7.5" z-index="1" size="7 7" opacity="0.7" image="http://files.virtit.fr/TrackMania/Images/EffectsIcons/Fragile.dds" />
<quad id="Fragile_EffectButton" class="quad-effects" pos="7.5 -7.5" z-index="0" size="9 9" opacity="0.5" style="UICommon64_1" substyle="BgFrame1" modulatecolor="000" scriptevents="1"/>
</frame>
</frame>
</frame>
<frame pos="2 -42">
<frame pos="0 0">
<label class="text-label" pos="0 0" z-index="0" size="22 5" text="Cruise" />
<slider id="Cruise_Slider" class="slider" pos="34 -0.3" value="0." range="-999 999" size="21 5" z-index="1" scriptevents="1" tooltip="test"/>
<entry id="Cruise_Entry" class="text-value" pos="53 0" z-index="0" size="8 5" default="0" focusareacolor1="00000000" focusareacolor2="00000000"/>
<label class="text-suffix" pos="55 0" z-index="0" size="4 4" text="kph" />
</frame>
<frame pos="0 -7">
<label class="text-label" pos="0 0" z-index="0" size="22 5" text="Acceleration" />
<slider id="AccelCoef_Slider" class="slider" pos="34 -0.3" value="1." range="0.01 1." size="21 5" z-index="1" scriptevents="1" tooltip="test"/>
<entry id="AccelCoef_Entry" class="text-value" pos="53 0" z-index="0" size="8 5" default="100"/>
<label class="text-suffix" pos="55 0" z-index="0" size="4 4" text="%" />
</frame>
<frame pos="0 -14">
<label class="text-label" pos="0 0" z-index="0" size="22 5" text="Adherence" />
<slider id="AdherenceCoef_Slider" class="slider" pos="34 -0.3" value="1." range="0.01 1." size="21 5" z-index="1" scriptevents="1" tooltip="test"/>
<entry id="AdherenceCoef_Entry" class="text-value" pos="53 0" z-index="0" size="8 5" default="100"/>
<label class="text-suffix" pos="55 0" z-index="0" size="10 5" text="%" />
</frame>
<frame pos="0 -21">
<label class="text-label" pos="0 0" z-index="0" size="22 5" text="Control" />
<slider id="ControlCoef_Slider" class="slider" pos="34 -0.3" value="1." range="0.01 1." size="21 5" z-index="1" scriptevents="1" tooltip="test"/>
<entry id="ControlCoef_Entry" class="text-value" pos="53 0" z-index="0" size="8 5" default="100"/>
<label class="text-suffix" pos="55 0" z-index="0" size="10 5" text="%" />
</frame>
<frame pos="0 -28">
<label class="text-label" pos="0 0" z-index="0" size="22 5" text="Gravity" />
<slider id="GravityCoef_Slider" class="slider" pos="34 -0.3" value="1." range="0.01 1." size="21 5" z-index="1" scriptevents="1" tooltip="test"/>
<entry id="GravityCoef_Entry" class="text-value" pos="53 0" z-index="0" size="8 5" default="100"/>
<label class="text-suffix" pos="55 0" z-index="0" size="10 5" text="%" />
</frame>
</frame>
<frame id="AllowSpectatorsControl_Frame" hidden="1" pos="5 -79">
<quad id="AllowSpectatorsControl_SettingButton" pos="0 0.5" z-index="1" size="5 5" bgcolor="FFF" halign="center" valign="center" style="UICommon64_1" substyle="CheckboxCircle_light" scriptevents="1" opacity="0.7" styleselected="1"/>
<label class="text-label" halign="left" pos="5 0.5" z-index="1" size="50 10" textprefix="$i" text="Allow a spectator to control your physics" autonewline="1"/>
</frame>
<frame id="Admin_Frame" hidden="1" pos="0 -88">
<quad pos="0 0" z-index="-1" size="60 10" bgcolor="000" opacity="0.5"/>
<frame pos="5 -5.5">
<quad id="ControledByAdmins_SettingButton" pos="0 0.5" z-index="1" size="5 5" bgcolor="FFF" halign="center" valign="center" style="UICommon64_1" substyle="CheckboxCircle_light" scriptevents="1" opacity="0.7" />
<label class="text-label" halign="left" pos="5 0.5" z-index="1" size="50 10" textprefix="$i" text="Control all players physics (admin only)" autonewline="1"/>
</frame>
</frame>
</frame>
</frame>
</manialink>
""";
Layers::Create("PhysicsDiscovery_UI", MLText);
Layers::SetType("PhysicsDiscovery_UI", CUILayer::EUILayerType::Normal);
Layers::Attach("PhysicsDiscovery_UI");
}