diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07129c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +media/ diff --git a/README.md b/README.md index 2a85bc8..f8fb616 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# tm-auto-hide-records \ No newline at end of file +# Auto-Hide Records + +An Openplanet plugin for Trackmania which hides the records UI when moving. + +Openplanet plugin url: https://openplanet.dev/plugin/autohiderecords + +License: Public Domain diff --git a/src/GameInfo.as b/src/GameInfo.as new file mode 100644 index 0000000..a857476 --- /dev/null +++ b/src/GameInfo.as @@ -0,0 +1,55 @@ +class GameInfo { + CTrackMania@ app; + + GameInfo() { + @app = cast(GetApp()); + } + + CTrackManiaNetwork@ Network { + get const { + return cast(this.app.Network); + } + // set {} + } + + CGamePlayground@ CurrentPlayground { + get const { + return cast(this.app.CurrentPlayground); + } + // set {} + } + + CGameTerminal@ GameTerminal { + get { + if (this.CurrentPlayground is null + || this.CurrentPlayground.GameTerminals.Length < 1) { + return null; + } + return this.CurrentPlayground.GameTerminals[0]; + } + // set {} + } + + NGameLoadProgress_SMgr@ LoadProgress { + get const { + return cast(this.app.LoadProgress); + } + // set {} + } + + MwFastBuffer UILayers { + get const { + return this.Network.ClientManiaAppPlayground.UILayers; + } + // set {} + } + + bool IsPlaying() { + auto network = Network; + auto playground = CurrentPlayground; + return playground !is null && + network.ClientManiaAppPlayground !is null && + network.ClientManiaAppPlayground.Playground !is null && + network.ClientManiaAppPlayground.UILayers.Length > 0; + } +} \ No newline at end of file diff --git a/src/HUDElement.as b/src/HUDElement.as new file mode 100644 index 0000000..707460b --- /dev/null +++ b/src/HUDElement.as @@ -0,0 +1,82 @@ +class HUDElement { + string Name; + string ModuleName; + int ModuleIndex; + string SubModuleName; + bool IsVisible = true; + bool GetVisible() { return this.IsVisible; } + void SetVisible(bool v) { this.IsVisible = v; } + + HUDElement(const string &in name, const string &in moduleName, const string &in subModuleName = "") { + this.Name = name; + this.ModuleName = moduleName; + this.ModuleIndex = -1; + this.SubModuleName = subModuleName; + } + + void ToggleVisible() { + this.SetVisible(!this.GetVisible()); + } + + void FindElement(MwFastBuffer &in uilayers) { + for (uint i = 0; i < uilayers.Length; i++) { + CGameUILayer@ curLayer = uilayers[i]; + int start = curLayer.ManialinkPageUtf8.IndexOf("<"); + int end = curLayer.ManialinkPageUtf8.IndexOf(">"); + if (start != -1 && end != -1) { + auto manialinkname = curLayer.ManialinkPageUtf8.SubStr(start, end); + if (manialinkname.Contains(this.ModuleName)) { + this.ModuleIndex = i; + return; // we don't need to continue further + } + } + } + this.ModuleIndex = -1; + } + + bool Exists(CGameUILayer@ curLayer) { + int start = curLayer.ManialinkPageUtf8.IndexOf("<"); + int end = curLayer.ManialinkPageUtf8.IndexOf(">"); + if (start != -1 && end != -1) { + auto manialinkname = curLayer.ManialinkPageUtf8.SubStr(start, end); + if (manialinkname.Contains(this.ModuleName)) { + return true; + } + } + this.ModuleIndex = -1; + return false; + } + + void UpdateVisibilty(CGameUILayer@ layer) { + if (this.Exists(layer)) { + if (this.ModuleName != "" && this.SubModuleName == "") { + if (this.GetVisible() != layer.IsVisible) { + layer.IsVisible = !layer.IsVisible; + } + } else if (this.ModuleName != "" && this.SubModuleName != "") { + CControlFrame@ c = cast(layer.LocalPage.GetFirstChild(this.SubModuleName).Control); + if (c !is null) { + array frames = { c }; + while (!frames.IsEmpty()) { + auto children = frames[0].Childs; + for (uint j = 0; j < children.Length; j++) { + if (Reflection::TypeOf(children[j]).Name == "CControlFrame") { + frames.InsertLast(cast(children[j])); + } else { + auto subModule = cast(children[j]); + if (this.GetVisible() && !subModule.IsVisible) { + subModule.Show(); + } else if (!this.GetVisible() && subModule.IsVisible) { + subModule.Hide(); + } + } + } + frames.RemoveAt(0); + } + } else { + error("SubModule could not be found"); + } + } + } + } +} diff --git a/src/RecordHUDElement.as b/src/RecordHUDElement.as new file mode 100644 index 0000000..7e90fdd --- /dev/null +++ b/src/RecordHUDElement.as @@ -0,0 +1,6 @@ +class RecordHUDElement: HUDElement { + RecordHUDElement() { + super("Records", "UIModule_Race_Record", ""); + } +} +RecordHUDElement@ recordHud = RecordHUDElement(); diff --git a/src/info.toml b/src/info.toml new file mode 100644 index 0000000..a9b6a2c --- /dev/null +++ b/src/info.toml @@ -0,0 +1,8 @@ +[meta] +name = "Auto-Hide Records" +author = "Dr Damage" +category = "Overlay" +version = "1.0.0" + +[script] +dependencies = [ "VehicleState" ] diff --git a/src/main.as b/src/main.as new file mode 100644 index 0000000..0aadaa5 --- /dev/null +++ b/src/main.as @@ -0,0 +1,123 @@ +// constants +const float SPEED_THRESHOLD = 1.0f; +const int MOUSE_MOVE_HIDE_DELAY = 3500; + +// HUDPicker constants +const string HUDPICKER_ID = "HUDPicker"; +const string HUDPICKER_RECORD_VISIBLE_VARNAME = "recordVisible"; + +// global vars +Meta::Plugin@ hudPickerPlugin = Meta::GetPluginFromID(HUDPICKER_ID); +GameInfo@ gameInfo; +float ticker = 0; + +// settings definition +[Setting name="Is Enabled" category="General"] +bool AutoHideRecords = true; + +// renders the setting in the plugins dropdown menu +void RenderMenu() +{ + if (UI::MenuItem(Icons::EyeSlash + " " + "Auto-Hide Records", "", AutoHideRecords)) { + AutoHideRecords = !AutoHideRecords; + } +} + +// HUD Picker helpers +Meta::PluginSetting@ findHudPickerRecordsVisibleSetting(array settings) { + for (int i = 0; i < int(settings.Length); i++) { + if ( + settings[i].VarName == HUDPICKER_RECORD_VISIBLE_VARNAME + && settings[i].Type == Meta::PluginSettingType::Bool + ) { + return settings[i]; + } + } + return null; +} +bool IsHudPickerRecordsHidden() { + if ( + hudPickerPlugin !is null + && hudPickerPlugin.Enabled + ) { + auto settings = hudPickerPlugin.GetSettings(); + auto setting = findHudPickerRecordsVisibleSetting(settings); + if (setting !is null) { + bool settingValue = setting.ReadBool(); + return !settingValue; + } + } + return false; +} + +void UpdateTicker(float dt) { + ticker -= dt; + if (ticker < 0) { + ticker = 0; + } +} + +bool ShouldRunPlugin() { + return ( + AutoHideRecords // plugin has to be enabled + && gameInfo.IsPlaying() // should be on a map + && !IsHudPickerRecordsHidden() // don't do anything if the records element is hidden via HUD Picker + && gameInfo.LoadProgress.State != NGameLoadProgress_SMgr::EState::Displayed // wait until playground finishes loading + ); +} + +void OnMouseMove(int x, int y) { +#if TMNEXT + if (!ShouldRunPlugin()) { + return; + } + ticker = MOUSE_MOVE_HIDE_DELAY; +#endif +} + +// init +void Main() { +#if TMNEXT + @gameInfo = GameInfo(); +#endif +} + +void Update(float dt) { +#if TMNEXT + if (!ShouldRunPlugin()) { + return; + } + // if index not set or out of bounds try to find the record ui element inside the UI layers collection and store the index + if (recordHud.ModuleIndex == -1 || recordHud.ModuleIndex >= int(gameInfo.UILayers.Length)) { + recordHud.FindElement(gameInfo.UILayers); + // if not found return + if (recordHud.ModuleIndex == -1) { + return; + } + } + auto gameTerminal = gameInfo.GameTerminal; + auto uiLayer = gameInfo.UILayers[recordHud.ModuleIndex]; + auto vehicleState = VehicleState::ViewingPlayerState(); + float speed = 0; + + if (vehicleState !is null) { + speed = vehicleState.FrontSpeed; + } + + + bool shouldShowHud = ( + ticker > 0 // if mouse was moved + || ( + gameTerminal !is null + && gameTerminal.UISequence_Current != CGamePlaygroundUIConfig::EUISequence::Playing // if the current game state isn't Playing + ) || speed < SPEED_THRESHOLD // if the speed is less than the threshold set + ); + + recordHud.SetVisible(shouldShowHud); + recordHud.UpdateVisibilty(uiLayer); + + if (ticker > 0) { + UpdateTicker(dt); + } +#endif +}