├── Resources ├── online.jpg ├── standard.jpg ├── repo-banner.jpg └── uitweaks-logo-pdn.jpg ├── UITweaks ├── Directory.Build.props ├── manifest.json ├── Models │ ├── UITweaksConfigBase.cs │ ├── PreviewPanel.cs │ └── PanelDecoratorBase.cs ├── Views │ ├── ObjectPreview.bsml │ ├── Info.bsml │ └── ModSettings.bsml ├── Utilities │ ├── RainbowEffectManager.cs │ ├── MultiplayerEnergyBarCoroutineHandler.cs │ ├── SettableSettings │ │ ├── UITweaksSettingsWrapper.cs │ │ └── UITweaksSettableSettings.cs │ ├── Utilities.cs │ └── HSBColor.cs ├── Installers │ ├── TweaksMenuInstaller.cs │ ├── TweaksPanelDecoratorInstaller.cs │ └── TweaksAppInstaller.cs ├── PluginConfig.cs ├── UI │ ├── MenuButtonManager.cs │ ├── UITweaksFlowCoordinator.cs │ ├── ModInfoViewController.cs │ ├── MockMultiplayerPositionPanel.cs │ ├── SettingsPanelObjectGrabber.cs │ ├── ModSettingsViewController.cs │ └── ObjectPreviewViewController.cs ├── Config │ ├── EnergyConfig.cs │ ├── MultiplierConfig.cs │ ├── ProgressConfig.cs │ ├── PositionConfig.cs │ ├── ComboConfig.cs │ └── MiscConfig.cs ├── Plugin.cs ├── Decorators │ ├── ComboPanelDecorator.cs │ ├── SongProgressPanelDecorator.cs │ ├── MultiplayerPositionPanelDecorator.cs │ ├── EnergyBarPanelDecorator.cs │ ├── ScoreMultiplierPanelDecorator.cs │ └── ExtraPanelDecorator.cs └── UITweaks.csproj ├── .gitignore ├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── UITweaks.sln └── CONTRIBUTING.md /Resources/online.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Exomanz/UITweaks/HEAD/Resources/online.jpg -------------------------------------------------------------------------------- /Resources/standard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Exomanz/UITweaks/HEAD/Resources/standard.jpg -------------------------------------------------------------------------------- /Resources/repo-banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Exomanz/UITweaks/HEAD/Resources/repo-banner.jpg -------------------------------------------------------------------------------- /Resources/uitweaks-logo-pdn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Exomanz/UITweaks/HEAD/Resources/uitweaks-logo-pdn.jpg -------------------------------------------------------------------------------- /UITweaks/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | True 6 | BSIPA 7 | 8 | -------------------------------------------------------------------------------- /UITweaks/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json", 3 | "id": "UITweaks", 4 | "name": "UITweaks", 5 | "author": "Exomanz", 6 | "version": "4.0.5", 7 | "description": "Add some color to your in-game HUD!", 8 | "gameVersion": "1.38.0", 9 | "dependsOn": { 10 | "BSIPA": "^4.3.5", 11 | "SiraUtil": "^3.1.12", 12 | "BeatSaberMarkupLanguage": "^1.12.3" 13 | } 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /UITweaks/obj/Release 6 | /UITweaks/obj 7 | /UITweaks/bin/Release 8 | /UITweaks/bin/Debug 9 | /UITweaks/UITweaks.csproj.user 10 | /.vs/UITweaks 11 | /.vs/ 12 | /UITweaks/.vs/ 13 | ./Wiki/UITweaks.wiki 14 | /Resources/*.pdn -------------------------------------------------------------------------------- /UITweaks/Models/UITweaksConfigBase.cs: -------------------------------------------------------------------------------- 1 | namespace UITweaks.Models 2 | { 3 | /// 4 | /// Helper class that allows for easy Zenject-ification. 5 | ///

All configs inherit this class. 6 | ///
7 | public abstract class UITweaksConfigBase 8 | { 9 | /// 10 | /// Controls whether a PanelDecorator is active in the given context. 11 | /// 12 | public virtual bool Enabled { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: exo_manz 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /UITweaks/Views/ObjectPreview.bsml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /UITweaks/Utilities/RainbowEffectManager.cs: -------------------------------------------------------------------------------- 1 | using UITweaks.Config; 2 | using UnityEngine; 3 | using Zenject; 4 | 5 | namespace UITweaks.Utilities 6 | { 7 | internal class RainbowEffectManager : ITickable 8 | { 9 | [Inject] private readonly MiscConfig miscConfig; 10 | 11 | /// 12 | /// The which controls the rainbow effect that UITweaks uses. All objects which use this effect will be in sync. 13 | /// 14 | /// This property is updated every frame. 15 | public Color Rainbow { get; private set; } 16 | 17 | [Inject] public RainbowEffectManager() { } 18 | 19 | public void Tick() 20 | { 21 | this.Rainbow = new HSBColor( 22 | Mathf.PingPong(Time.time * miscConfig.GlobalRainbowSpeed, 1), 23 | 1, 24 | 1) 25 | .ToColor(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /UITweaks/Installers/TweaksMenuInstaller.cs: -------------------------------------------------------------------------------- 1 | using UITweaks.UI; 2 | using UnityEngine; 3 | using Zenject; 4 | 5 | namespace UITweaks.Installers 6 | { 7 | public class TweaksMenuInstaller : Installer 8 | { 9 | public override void InstallBindings() 10 | { 11 | Container.Bind().FromNewComponentAsViewController().AsSingle(); 12 | Container.Bind().FromNewComponentAsViewController().AsSingle(); 13 | Container.Bind().FromNewComponentAsViewController().AsSingle(); 14 | Container.Bind().FromNewComponentOn(new GameObject("SettingsPanelObjectGrabber")).AsSingle(); 15 | 16 | Container.Bind().FromNewComponentOn(new GameObject("UITweaksFlowCoordinator")).AsSingle(); 17 | Container.BindInterfacesAndSelfTo().AsSingle(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /UITweaks/PluginConfig.cs: -------------------------------------------------------------------------------- 1 | using IPA.Config.Stores.Attributes; 2 | using UITweaks.Config; 3 | 4 | namespace UITweaks 5 | { 6 | public class PluginConfig 7 | { 8 | public virtual bool AllowAprilFools { get; set; } = true; 9 | 10 | [NonNullable] 11 | public virtual MultiplierConfig Multiplier { get; set; } = new MultiplierConfig(); 12 | 13 | [NonNullable] 14 | public virtual ComboConfig Combo { get; set; } = new ComboConfig(); 15 | 16 | [NonNullable] 17 | public virtual EnergyConfig Energy { get; set; } = new EnergyConfig(); 18 | 19 | [NonNullable] 20 | public virtual ProgressConfig Progress { get; set; } = new ProgressConfig(); 21 | 22 | [NonNullable] 23 | public virtual PositionConfig Position { get; set; } = new PositionConfig(); 24 | 25 | [NonNullable] 26 | public virtual MiscConfig Misc { get; set; } = new MiscConfig(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /UITweaks/UI/MenuButtonManager.cs: -------------------------------------------------------------------------------- 1 | using BeatSaberMarkupLanguage.MenuButtons; 2 | using HMUI; 3 | using System; 4 | using Zenject; 5 | 6 | namespace UITweaks.UI 7 | { 8 | internal class MenuButtonManager : IInitializable, IDisposable 9 | { 10 | [Inject] private readonly MainFlowCoordinator mainFlowCoordinator; 11 | [Inject] private readonly UITweaksFlowCoordinator modFlowCoordinator; 12 | private MenuButton button; 13 | 14 | public void Initialize() 15 | { 16 | button = new MenuButton("UI Tweaks", "Spice up your HUD!", () => 17 | { 18 | mainFlowCoordinator.PresentFlowCoordinator(modFlowCoordinator, null, ViewController.AnimationDirection.Vertical); 19 | }); 20 | MenuButtons.Instance.RegisterButton(button); 21 | } 22 | 23 | public void Dispose() 24 | { 25 | if (button != null) 26 | MenuButtons.Instance.UnregisterButton(button); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /UITweaks/Utilities/MultiplayerEnergyBarCoroutineHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UITweaks.PanelModifiers; 3 | using UnityEngine; 4 | using Zenject; 5 | 6 | namespace UITweaks.Utilities 7 | { 8 | internal class MultiplayerEnergyBarCoroutineHandler : MonoBehaviour 9 | { 10 | [Inject] private readonly EnergyBarPanelDecorator energyBarPanelDecorator; 11 | [Inject] private readonly GameplayModifiers gameplayModifiers; 12 | 13 | public void Start() 14 | { 15 | transform.SetParent(base.transform); 16 | gameObject.SetActive(true); 17 | base.StartCoroutine(PrepareMultiplayerEnergyBarColorsForEnergyType(gameplayModifiers.energyType)); 18 | } 19 | 20 | private IEnumerator PrepareMultiplayerEnergyBarColorsForEnergyType(GameplayModifiers.EnergyType energyType) 21 | { 22 | yield return StartCoroutine(energyBarPanelDecorator.BatteryEnergyAndOneLifeSetup(energyType)); 23 | gameObject.SetActive(false); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Exomanz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /UITweaks/Models/PreviewPanel.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace UITweaks.Models 4 | { 5 | /// 6 | /// Helper class which contains some useful properties for preview panel iteration and code reusability. 7 | /// 8 | public class PreviewPanel 9 | { 10 | /// 11 | /// The parent of the panel being previewed. 12 | /// 13 | public GameObject Panel { get; set; } 14 | 15 | /// 16 | /// The settings tab index this panel is active on. 17 | /// 18 | public int ActiveTab { get; set; } 19 | 20 | /// 21 | /// Creates a new instance of a with a given parent object and settings tab index. 22 | /// 23 | /// The index of the settings page this panel will appear on. 24 | /// The parent of the panel being previewed. 25 | public PreviewPanel(int activeTab, GameObject panel) 26 | { 27 | ActiveTab = activeTab; 28 | Panel = panel; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /UITweaks/UI/UITweaksFlowCoordinator.cs: -------------------------------------------------------------------------------- 1 | using BeatSaberMarkupLanguage; 2 | using HMUI; 3 | using Zenject; 4 | 5 | namespace UITweaks.UI 6 | { 7 | internal class UITweaksFlowCoordinator : FlowCoordinator 8 | { 9 | [Inject] private readonly MainFlowCoordinator mainFlowCoordinator; 10 | [Inject] private readonly ModSettingsViewController settingsView; 11 | [Inject] private readonly ModInfoViewController infoView; 12 | [Inject] private readonly ObjectPreviewViewController previewView; 13 | 14 | protected override void DidActivate(bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) 15 | { 16 | if (firstActivation && addedToHierarchy) 17 | { 18 | base.SetTitle("UITweaks"); 19 | base.ProvideInitialViewControllers(settingsView, infoView, previewView); 20 | base.showBackButton = true; 21 | } 22 | } 23 | 24 | protected override void BackButtonWasPressed(ViewController topViewController) 25 | { 26 | base.BackButtonWasPressed(topViewController); 27 | mainFlowCoordinator.DismissFlowCoordinator(this, null, ViewController.AnimationDirection.Vertical, false); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /UITweaks/Config/EnergyConfig.cs: -------------------------------------------------------------------------------- 1 | using IPA.Config.Stores.Attributes; 2 | using IPA.Config.Stores.Converters; 3 | using UITweaks.Models; 4 | using UnityEngine; 5 | 6 | namespace UITweaks.Config 7 | { 8 | public class EnergyConfig : UITweaksConfigBase 9 | { 10 | public override bool Enabled { get; set; } = true; 11 | 12 | /// 13 | /// If set to , the Energy Bar will play a rainbow animation when you reach full energy. 14 | /// 15 | public virtual bool RainbowOnFullEnergy { get; set; } = false; 16 | 17 | /// 18 | /// The of the leftmost anchor point in the energy bar (0%). 19 | /// 20 | [UseConverter(typeof(HexColorConverter))] 21 | public virtual Color Low { get; set; } = Color.red; 22 | 23 | /// 24 | /// The of the center anchor point in the energy bar (50%). 25 | /// 26 | [UseConverter(typeof(HexColorConverter))] 27 | public virtual Color Mid { get; set; } = Color.yellow; 28 | 29 | /// 30 | /// The of the rightmost anchor point in the energy bar (100%). 31 | /// 32 | [UseConverter(typeof(HexColorConverter))] 33 | public virtual Color High { get; set; } = Color.green; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | UITweaks Logo 2 | 3 | # 🛠️ UITweaks 4 | A Beat Saber mod that aims to bring more color to the in-game HUD! 5 | 6 | [![GitHub Release](https://img.shields.io/github/v/release/Exomanz/UITweaks?style=flat-square&color=cornflowerblue)](https://github.com/Exomanz/UITweaks/releases/latest) 7 | [![GitHub Commits Since Latest Release (branch)](https://img.shields.io/github/commits-since/Exomanz/UITweaks/latest?style=flat-square&color=green)](https://github.com/Exomanz/UITweaks/commits) 8 | 9 | ## Dependencies: 10 | - BSIPA v4.3.5+ 11 | - SiraUtil v3.1.12+ 12 | - BeatSaberMarkupLanguage v1.12.3+ 13 | 14 | ## Features 15 | - Multiplier ring colors, as well as an option to smoothly transition between them, and an optional rainbow animation on full 8x multiplier. 16 | - Energy bar colors corresponding to your current energy, and an optional rainbow animation on full. 17 | - Combo FC line colors, including optional gradient lines. 18 | - Colors for the progress bar, background, and slider knob. There also exists an option to smoothly transition between two colors as the song progresses. 19 | - Multiplayer player position panel colors for each position, an option to hide the first place animation, and an option for the static panel to use a static color. 20 | - And so much more! 21 | 22 | ## Contributing 23 | Please read the [contributing file](CONTRIBUTING.md) for more information. 24 | -------------------------------------------------------------------------------- /UITweaks/UI/ModInfoViewController.cs: -------------------------------------------------------------------------------- 1 | using BeatSaberMarkupLanguage.Attributes; 2 | using BeatSaberMarkupLanguage.ViewControllers; 3 | using IPA.Loader; 4 | using SiraUtil.Logging; 5 | using SiraUtil.Zenject; 6 | using UnityEngine; 7 | using Zenject; 8 | 9 | namespace UITweaks.UI 10 | { 11 | [ViewDefinition("UITweaks.Views.Info.bsml")] 12 | [HotReload(RelativePathToLayout = @"..\Views\Info.bsml")] 13 | public class ModInfoViewController : BSMLAutomaticViewController 14 | { 15 | [Inject] private readonly SiraLog logger; 16 | private PluginMetadata meta; 17 | 18 | [Inject] 19 | internal void Construct(UBinder metadata) 20 | { 21 | meta = metadata.Value; 22 | } 23 | 24 | [UIValue("version-text")] 25 | private string Version 26 | { 27 | get => $"Version : {meta.HVersion}"; 28 | } 29 | 30 | [UIAction("open-gh-source")] 31 | internal void OpenSourceLink() => Application.OpenURL("https://github.com/Exomanz/UITweaks"); 32 | 33 | [UIAction("open-kofi")] 34 | internal void OpenDonateLink() => Application.OpenURL("https://ko-fi.com/exo_manz"); 35 | 36 | [UIAction("open-changelog")] 37 | internal void OpenChangelogLink() => Application.OpenURL("https://github.com/Exomanz/UITweaks/commits/"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /UITweaks.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31919.166 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UITweaks", "UITweaks\UITweaks.csproj", "{4B5D5899-57C0-4FB5-891A-E83DD3071C69}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Heck-Debug|Any CPU = Heck-Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {4B5D5899-57C0-4FB5-891A-E83DD3071C69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {4B5D5899-57C0-4FB5-891A-E83DD3071C69}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {4B5D5899-57C0-4FB5-891A-E83DD3071C69}.Heck-Debug|Any CPU.ActiveCfg = Heck-Debug|Any CPU 18 | {4B5D5899-57C0-4FB5-891A-E83DD3071C69}.Heck-Debug|Any CPU.Build.0 = Heck-Debug|Any CPU 19 | {4B5D5899-57C0-4FB5-891A-E83DD3071C69}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {4B5D5899-57C0-4FB5-891A-E83DD3071C69}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(SolutionProperties) = preSolution 23 | HideSolutionNode = FALSE 24 | EndGlobalSection 25 | GlobalSection(ExtensibilityGlobals) = postSolution 26 | SolutionGuid = {EC8C1BED-52E9-4AB2-B7EA-CCD6FADE45DB} 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /UITweaks/Utilities/SettableSettings/UITweaksSettingsWrapper.cs: -------------------------------------------------------------------------------- 1 | namespace UITweaks.Utilities.SettableSettings 2 | { 3 | #if HECK 4 | using Heck.SettingsSetter; 5 | using System; 6 | using System.Reflection; 7 | using UnityEngine; 8 | public class UITweaksSettingsWrapper : ISettableSetting 9 | { 10 | private readonly PropertyInfo settingsProperty; 11 | private readonly object settingsInstance; 12 | 13 | private object originalValue; 14 | 15 | public string GroupName { get; } 16 | public string FieldName { get; } 17 | 18 | public UITweaksSettingsWrapper(string groupName, string fieldName, PropertyInfo settingsProperty, object settingsInstance) 19 | { 20 | GroupName = groupName; 21 | FieldName = fieldName; 22 | 23 | this.settingsProperty = settingsProperty; 24 | this.settingsInstance = settingsInstance; 25 | } 26 | 27 | public object TrueValue => settingsProperty.GetValue(settingsInstance); 28 | 29 | public void SetTemporary(object tempValue) 30 | { 31 | if (tempValue != null) 32 | { 33 | originalValue = settingsProperty.GetValue(settingsInstance); 34 | settingsProperty.SetValue(settingsInstance, tempValue); 35 | } 36 | 37 | else if (originalValue != null) 38 | { 39 | settingsProperty.SetValue(settingsInstance, originalValue); 40 | originalValue = null; 41 | } 42 | } 43 | } 44 | #endif 45 | } -------------------------------------------------------------------------------- /UITweaks/Plugin.cs: -------------------------------------------------------------------------------- 1 | using IPA; 2 | using IPA.Config.Stores; 3 | using IPA.Utilities; 4 | using SiraUtil.Zenject; 5 | using System; 6 | using System.Linq; 7 | using UITweaks.Installers; 8 | using IPAConfig = IPA.Config.Config; 9 | using IPALogger = IPA.Logging.Logger; 10 | 11 | namespace UITweaks 12 | { 13 | [Plugin(RuntimeOptions.DynamicInit), NoEnableDisable] 14 | public class Plugin 15 | { 16 | public static bool APRIL_FOOLS 17 | { 18 | get 19 | { 20 | if (Environment.GetCommandLineArgs().Any(x => x.ToLower() == "--uitweaks-aprilfools")) 21 | return true; 22 | 23 | DateTime time = Utils.CurrentTime(); 24 | return time.Month == 4 && time.Day == 1; 25 | } 26 | } 27 | 28 | [Init] 29 | public Plugin(IPALogger logger, IPAConfig config, Zenjector zenject) 30 | { 31 | zenject.UseLogger(logger); 32 | zenject.UseMetadataBinder(); 33 | 34 | // Singleplayer and Campaign 35 | zenject.Expose("Environment"); 36 | 37 | // Multiplayer 38 | zenject.Expose("IsActiveObjects"); 39 | zenject.Expose("IsActiveObjects"); 40 | 41 | zenject.Install(Location.App, config.Generated()); 42 | zenject.Install(Location.Menu); 43 | zenject.Install(Location.Player); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /UITweaks/UI/MockMultiplayerPositionPanel.cs: -------------------------------------------------------------------------------- 1 | using HMUI; 2 | using UnityEngine; 3 | 4 | namespace UITweaks.UI 5 | { 6 | public class MockMultiplayerPositionPanel : MonoBehaviour 7 | { 8 | [SerializeField] public bool IsSetup { get; private set; } = false; 9 | [SerializeField] public CurvedTextMeshPro positionText; 10 | [SerializeField] public CurvedTextMeshPro playerCountText; 11 | 12 | public void Start() 13 | { 14 | gameObject.SetActive(false); 15 | 16 | var pt = new GameObject("PositionText").AddComponent(); 17 | pt.transform.SetParent(this.transform, false); 18 | pt.alignment = TMPro.TextAlignmentOptions.Center; 19 | pt.fontStyle = TMPro.FontStyles.Italic; 20 | pt.text = "1"; 21 | pt.transform.localPosition = new Vector3(-20, 0, 0); 22 | 23 | var pct = new GameObject("PlayerCountText").AddComponent(); 24 | pct.transform.SetParent(this.transform, false); 25 | pct.alignment = TMPro.TextAlignmentOptions.Center; 26 | pct.fontStyle = TMPro.FontStyles.Italic; 27 | pct.text = "/ 5"; 28 | pct.transform.localPosition = new Vector3(10, 0, 0); 29 | 30 | positionText = pt; 31 | playerCountText = pct; 32 | 33 | gameObject.SetActive(true); 34 | gameObject.AddComponent(); 35 | transform.localScale = new Vector3(0.02f, 0.02f, 0.02f); 36 | transform.localPosition = new Vector3(-0.08f, 0, 0); 37 | 38 | IsSetup = true; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /UITweaks/Installers/TweaksPanelDecoratorInstaller.cs: -------------------------------------------------------------------------------- 1 | using UITweaks.Models; 2 | using UITweaks.PanelModifiers; 3 | using UITweaks.Utilities; 4 | using UnityEngine; 5 | using Zenject; 6 | 7 | namespace UITweaks.Installers 8 | { 9 | public class TweaksPanelDecoratorInstaller : Installer 10 | { 11 | [Inject] private readonly PluginConfig config; 12 | 13 | public override void InstallBindings() 14 | { 15 | bool isMultiplayer = Container.HasBinding(); 16 | 17 | BindPanelDecorator(); 18 | BindPanelDecorator(); 19 | BindPanelDecorator(); 20 | BindPanelDecorator(); 21 | BindPanelDecorator(); 22 | 23 | if (isMultiplayer) 24 | { 25 | BindPanelDecorator(); 26 | Container.Bind().FromNewComponentOn(new GameObject("MultiplayerEnergyBarCoroutineHandler")).AsSingle().NonLazy(); 27 | } 28 | 29 | if (Plugin.APRIL_FOOLS && config.AllowAprilFools) 30 | Container.Bind().FromNewComponentOn(new GameObject("UITweaks-AprilFoolsController")).AsSingle().NonLazy(); 31 | } 32 | 33 | /// 34 | /// Shorthand function for binding classes. 35 | /// 36 | /// Any class which derives from 37 | private void BindPanelDecorator() where T : PanelDecoratorBase 38 | { 39 | Container.Bind().FromNewComponentOn(new GameObject(typeof(T).Name)).AsSingle().NonLazy(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /UITweaks/Installers/TweaksAppInstaller.cs: -------------------------------------------------------------------------------- 1 | using UITweaks.Models; 2 | using UITweaks.Utilities; 3 | using UITweaks.Utilities.SettableSettings; 4 | using IPA.Loader; 5 | using Zenject; 6 | 7 | namespace UITweaks.Installers 8 | { 9 | public class TweaksAppInstaller : Installer 10 | { 11 | private readonly PluginConfig config; 12 | 13 | [Inject] 14 | public TweaksAppInstaller(PluginConfig pluginConfig) 15 | { 16 | config = pluginConfig; 17 | } 18 | 19 | public override void InstallBindings() 20 | { 21 | Container.Bind().FromInstance(config).AsCached(); 22 | BindConfig(config.Multiplier); 23 | BindConfig(config.Energy); 24 | BindConfig(config.Combo); 25 | BindConfig(config.Progress); 26 | BindConfig(config.Position); 27 | BindConfig(config.Misc); 28 | 29 | Container.BindInterfacesAndSelfTo().AsSingle(); 30 | 31 | #if HECK 32 | if (PluginManager.GetPlugin("Heck") != null) 33 | { 34 | Container.BindInterfacesAndSelfTo().AsSingle().NonLazy(); 35 | } 36 | #endif 37 | } 38 | 39 | /// 40 | /// Shorthand function for quickly binding configs in Zenject.

41 | ///
42 | /// Any class which derives from 43 | /// An instance of a . These are usually defined inside of 44 | private void BindConfig(T instance) where T : UITweaksConfigBase 45 | { 46 | Container.Bind().FromInstance(instance).AsCached(); 47 | Container.Bind().To().FromInstance(instance).AsCached(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /UITweaks/Config/MultiplierConfig.cs: -------------------------------------------------------------------------------- 1 | using IPA.Config.Stores.Attributes; 2 | using IPA.Config.Stores.Converters; 3 | using UITweaks.Models; 4 | using UnityEngine; 5 | 6 | namespace UITweaks.Config 7 | { 8 | public class MultiplierConfig : UITweaksConfigBase 9 | { 10 | public override bool Enabled { get; set; } = true; 11 | 12 | /// 13 | /// If set to , the multiplier ring will crossfade between each set for each stage. 14 | /// 15 | public virtual bool SmoothTransition { get; set; } = false; 16 | 17 | /// 18 | /// Setting this to will enable a rainbow animation when your combo reaches 8x. 19 | /// 20 | public virtual bool RainbowOnMaxMultiplier { get; set; } = true; 21 | 22 | /// 23 | /// The of the Multiplier ring when your combo is at 1x. 24 | /// 25 | [UseConverter(typeof(HexColorConverter))] 26 | public virtual Color One { get; set; } = Color.red; 27 | 28 | /// 29 | /// The of the Multiplier ring when your combo is at 2x. 30 | /// 31 | [UseConverter(typeof(HexColorConverter))] 32 | public virtual Color Two { get; set; } = Color.yellow; 33 | 34 | /// 35 | /// The of the Multiplier ring when your combo is at 4x. 36 | /// 37 | [UseConverter(typeof(HexColorConverter))] 38 | public virtual Color Four { get; set; } = Color.green; 39 | 40 | /// 41 | /// The of the Multiplier ring when your combo is at 8x and is set to . 42 | /// 43 | [UseConverter(typeof(HexColorConverter))] 44 | public virtual Color Eight { get; set; } = Color.cyan; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /UITweaks/Decorators/ComboPanelDecorator.cs: -------------------------------------------------------------------------------- 1 | using HMUI; 2 | using UITweaks.Config; 3 | using UITweaks.Models; 4 | using Zenject; 5 | 6 | namespace UITweaks.PanelModifiers 7 | { 8 | public class ComboPanelDecorator : PanelDecoratorBase 9 | { 10 | [Inject] private readonly ComboConfig comboConfig; 11 | 12 | private ComboUIController comboUIController; 13 | 14 | [Inject] 15 | protected override void Init() 16 | { 17 | comboUIController = base.gameHUDController.GetComponentInChildren(); 18 | ParentPanel = comboUIController?.gameObject; 19 | Config = comboConfig; 20 | transform.SetParent(ParentPanel?.transform); 21 | 22 | ModPanel(this); 23 | } 24 | 25 | protected override bool ModPanel(in PanelDecoratorBase decorator) 26 | { 27 | if (!base.ModPanel(this)) return false; 28 | 29 | ImageView[] fcLines; 30 | fcLines = ParentPanel.GetComponentsInChildren(); 31 | 32 | if (comboConfig.UseGradient) 33 | { 34 | fcLines[0].gradient = true; 35 | fcLines[1].gradient = true; 36 | 37 | fcLines[0].color0 = comboConfig.TopLeft; 38 | fcLines[0].color1 = comboConfig.TopRight; 39 | 40 | if (comboConfig.MirrorBottomLine) 41 | { 42 | fcLines[1].color0 = comboConfig.TopRight; 43 | fcLines[1].color1 = comboConfig.TopLeft; 44 | } 45 | else 46 | { 47 | fcLines[1].color0 = comboConfig.BottomLeft; 48 | fcLines[1].color1 = comboConfig.BottomRight; 49 | } 50 | } 51 | else 52 | { 53 | fcLines[0].color = comboConfig.TopLine; 54 | fcLines[1].color = comboConfig.BottomLine; 55 | } 56 | 57 | return true; 58 | } 59 | 60 | protected override void OnDestroy() => base.OnDestroy(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /UITweaks/Utilities/SettableSettings/UITweaksSettableSettings.cs: -------------------------------------------------------------------------------- 1 | namespace UITweaks.Utilities.SettableSettings 2 | { 3 | #if HECK 4 | using Heck.SettingsSetter; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | using UITweaks.Models; 9 | 10 | // Shoutout to Caeden117's Counters+ for providing a good example on how to register SettableSettings to Heck 11 | internal class UITweaksSettableSettings : IDisposable 12 | { 13 | public static bool HasRunBefore { get; private set; } = false; 14 | 15 | private const string groupIdentifier = "_uiTweaks"; 16 | 17 | private readonly BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; 18 | 19 | private static List settableSettings = new List(); 20 | 21 | public UITweaksSettableSettings(List configs) 22 | { 23 | if (HasRunBefore) return; 24 | HasRunBefore = true; 25 | 26 | Type settableSettingsType = typeof(UITweaksSettingsWrapper); 27 | 28 | configs.Remove(configs.Find((c) => c.GetType().Name == "MiscConfig")); 29 | 30 | foreach (var conf in configs) 31 | { 32 | var configType = conf.GetType(); 33 | var normalizedTypeName = configType.BaseType.Name; 34 | var enabledProperty = configType.GetProperty("Enabled", bindingFlags); 35 | 36 | ISettableSetting setting = Activator.CreateInstance(settableSettingsType, 37 | $"UITweaks - {normalizedTypeName}", enabledProperty.Name, enabledProperty, conf) 38 | as ISettableSetting; 39 | 40 | settableSettings.Add(setting); 41 | 42 | int indexOfConfig = normalizedTypeName.IndexOf("Config"); 43 | string heckFieldName = $"_{normalizedTypeName.Remove(indexOfConfig).ToLowerInvariant()}{enabledProperty.Name}"; 44 | SettingSetterSettableSettingsManager.RegisterSettableSetting(groupIdentifier, heckFieldName, setting); 45 | } 46 | } 47 | 48 | public void Dispose() 49 | { 50 | settableSettings.ForEach(x => x.SetTemporary(null)); 51 | } 52 | } 53 | #endif 54 | } -------------------------------------------------------------------------------- /UITweaks/Utilities/Utilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UITweaks.Utilities 4 | { 5 | internal static class Utilities 6 | { 7 | private static readonly Random Rand = new Random(Environment.TickCount + 2); 8 | 9 | /// 10 | /// Generates a random decimal between 0.0 and . 11 | /// 12 | /// 13 | /// A non-negative decimal greater than or equal to 0.0 and less than . 14 | public static decimal RandomDecimal(double max) 15 | { 16 | double sample = Rand.NextDouble(); 17 | decimal d = (decimal)(sample * max); 18 | return d; 19 | } 20 | 21 | /// 22 | /// Generates a random decimal between 0.0 and , trimmed to a set amount of decimal places. 23 | /// 24 | /// 25 | /// 26 | /// A non-negative decimal greater than or equal to 0.0 and less than , trimmed to decimal spaces. 27 | public static decimal RandomDecimal(double max, int decimalSpaces) 28 | { 29 | double sample = Rand.NextDouble(); 30 | decimal d = (decimal)(sample * max); 31 | decimal d2 = decimal.Round(d, decimalSpaces); 32 | 33 | return d2; 34 | } 35 | 36 | /// 37 | /// Generates a random decimal between and , trimmed to a set amount of decimal places. 38 | /// 39 | /// 40 | /// 41 | /// 42 | /// A non-negative decimal greater than or equal to and less than , rounded to decimal spaces. 43 | public static decimal RandomDecimal(double min, double max, int decimalSpaces) 44 | { 45 | double sample = Rand.NextDouble(); 46 | decimal d = (decimal)(sample * (max - min)) + (decimal)min; 47 | decimal d2 = decimal.Round(d, decimalSpaces); 48 | 49 | return d2; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /UITweaks/Config/ProgressConfig.cs: -------------------------------------------------------------------------------- 1 | using IPA.Config.Stores.Attributes; 2 | using IPA.Config.Stores.Converters; 3 | using UITweaks.Models; 4 | using UnityEngine; 5 | 6 | namespace UITweaks.Config 7 | { 8 | public class ProgressConfig : UITweaksConfigBase 9 | { 10 | public enum DisplayType 11 | { 12 | Fixed = 0, 13 | Fade = 1, 14 | } 15 | 16 | public override bool Enabled { get; set; } = true; 17 | 18 | /// 19 | /// The of the Progress Bar's fill section. 20 | /// 21 | [UseConverter(typeof(HexColorConverter))] 22 | public virtual Color Fill { get; set; } = Color.white; 23 | 24 | /// 25 | /// The of the Progress Bar's background. 26 | /// 27 | [UseConverter(typeof(HexColorConverter))] 28 | public virtual Color BG { get; set; } = Color.white; 29 | 30 | /// 31 | /// The of the Progress Bar's handle, which is anchored to the Fill section. 32 | /// 33 | [UseConverter(typeof(HexColorConverter))] 34 | public virtual Color Handle { get; set; } = Color.white; 35 | 36 | /// 37 | /// Controls the mode in which the progress bar is decorated. 38 | /// 39 | /// Fixed: All components will be statically colored for the entire song. 40 | /// Fade: The Background component of the progress bar will fade between and as the song progresses. 41 | /// 42 | /// 43 | public virtual DisplayType Mode { get; set; } = DisplayType.Fixed; 44 | 45 | /// 46 | /// The of the progress bar fill section at the start of the song. 47 | ///

This will only take effect if is . 48 | ///
49 | [UseConverter(typeof(HexColorConverter))] 50 | public virtual Color StartColor { get; set; } = Color.red; 51 | 52 | /// 53 | /// The of the Progress Bar fill section at the end of the song. 54 | ///

This will only take effect if is . 55 | ///
56 | [UseConverter(typeof(HexColorConverter))] 57 | public virtual Color EndColor { get; set; } = Color.green; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /UITweaks/Config/PositionConfig.cs: -------------------------------------------------------------------------------- 1 | using IPA.Config.Stores.Attributes; 2 | using IPA.Config.Stores.Converters; 3 | using UITweaks.Models; 4 | using UnityEngine; 5 | 6 | namespace UITweaks.Config 7 | { 8 | public class PositionConfig : UITweaksConfigBase 9 | { 10 | public override bool Enabled { get; set; } = true; 11 | 12 | /// 13 | /// If set to , the 1st place animation object will be hidden. 14 | /// 15 | public virtual bool HideFirstPlaceAnimation { get; set; } = false; 16 | 17 | /// 18 | /// Controls the rainbow effect of the PanelDecorator when in first place. 19 | /// 20 | public virtual bool RainbowOnFirstPlace { get; set; } = false; 21 | 22 | /// 23 | /// If set to , the player count panel will use a static color regardless of your position. 24 | /// 25 | public virtual bool UseStaticColorForStaticPanel { get; set; } = false; 26 | 27 | /// 28 | /// The of the player count section of the Position Panel. 29 | /// 30 | /// This will only take effect if is . 31 | [UseConverter(typeof(HexColorConverter))] 32 | public virtual Color StaticPanelColor { get; set; } = Color.white; 33 | 34 | /// 35 | /// The of the Position Panel when you are in first place. 36 | /// 37 | [UseConverter(typeof(HexColorConverter))] 38 | public virtual Color First { get; set; } = Color.cyan; 39 | 40 | /// 41 | /// The of the Position Panel when you are in second place. 42 | /// 43 | [UseConverter(typeof(HexColorConverter))] 44 | public virtual Color Second { get; set; } = Color.green; 45 | 46 | /// 47 | /// The of the Position Panel when you are in third place. 48 | /// 49 | [UseConverter(typeof(HexColorConverter))] 50 | public virtual Color Third { get; set; } = Color.yellow; 51 | 52 | /// 53 | /// The of the Position Panel when you are in fourth place. 54 | /// 55 | [UseConverter(typeof(HexColorConverter))] 56 | public virtual Color Fourth { get; set; } = new Color(1f, 0.5f, 0f); 57 | 58 | /// 59 | /// The of the Position Panel when you are in fifth place. 60 | /// 61 | [UseConverter(typeof(HexColorConverter))] 62 | public virtual Color Fifth { get; set; } = Color.red; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /UITweaks/Config/ComboConfig.cs: -------------------------------------------------------------------------------- 1 | using IPA.Config.Stores.Attributes; 2 | using IPA.Config.Stores.Converters; 3 | using UITweaks.Models; 4 | using UnityEngine; 5 | 6 | namespace UITweaks.Config 7 | { 8 | public class ComboConfig : UITweaksConfigBase 9 | { 10 | public override bool Enabled { get; set; } = true; 11 | 12 | /// 13 | /// The of the top Combo line. 14 | /// 15 | [UseConverter(typeof(HexColorConverter))] 16 | public virtual Color TopLine { get; set; } = Color.cyan; 17 | 18 | /// 19 | /// The of the bottom Combo line. 20 | /// 21 | [UseConverter(typeof(HexColorConverter))] 22 | public virtual Color BottomLine { get; set; } = Color.cyan; 23 | 24 | /// 25 | /// If set to , each Combo line will have a gradient. 26 | /// 27 | public virtual bool UseGradient { get; set; } = true; 28 | 29 | /// 30 | /// If set to , the bottom Combo line will reflect the top Combo line horizontally. 31 | /// 32 | /// This will only take effect if is . 33 | public virtual bool MirrorBottomLine { get; set; } = true; 34 | 35 | /// 36 | /// The left of the top Combo line. 37 | /// 38 | /// This will only take effect if is . 39 | [UseConverter(typeof(HexColorConverter))] 40 | public virtual Color TopLeft { get; set; } = Color.red; 41 | 42 | /// 43 | /// The right of the top Combo line. 44 | /// 45 | /// This will only take effect if is . 46 | [UseConverter(typeof(HexColorConverter))] 47 | public virtual Color TopRight { get; set; } = Color.yellow; 48 | 49 | /// 50 | /// The left of the bottom Combo line. 51 | /// 52 | /// This will only take effect if is and is . 53 | [UseConverter(typeof(HexColorConverter))] 54 | public virtual Color BottomLeft { get; set; } = Color.white; 55 | 56 | /// 57 | /// The right of the bottom Combo line. 58 | /// 59 | /// This will only take effect if is and is . 60 | [UseConverter(typeof(HexColorConverter))] 61 | public virtual Color BottomRight { get; set; } = Color.white; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /UITweaks/Decorators/SongProgressPanelDecorator.cs: -------------------------------------------------------------------------------- 1 | using HMUI; 2 | using System.Collections.Generic; 3 | using UITweaks.Config; 4 | using UITweaks.Models; 5 | using Zenject; 6 | 7 | namespace UITweaks.PanelModifiers 8 | { 9 | public class SongProgressPanelDecorator : PanelDecoratorBase 10 | { 11 | [InjectOptional] private readonly StandardGameplaySceneSetupData gameplaySceneSetupData; 12 | [Inject] private readonly CoreGameHUDController.InitData coreGameHudInitData; 13 | [Inject] private readonly AudioTimeSyncController audioTimeSyncController; 14 | [Inject] private readonly ProgressConfig progressConfig; 15 | 16 | private SongProgressUIController songProgressUIController; 17 | private readonly List barComponents = new List(); 18 | 19 | [Inject] 20 | protected override void Init() 21 | { 22 | songProgressUIController = base.gameHUDController.GetComponentInChildren(); 23 | ParentPanel = songProgressUIController?.gameObject; 24 | Config = progressConfig; 25 | transform.SetParent(ParentPanel?.transform); 26 | 27 | ModPanel(this); 28 | } 29 | 30 | protected override bool ModPanel(in PanelDecoratorBase decorator) 31 | { 32 | if (!coreGameHudInitData.advancedHUD) 33 | { 34 | this.CanBeUsedSafely = false; 35 | return false; 36 | } 37 | 38 | if (!base.ModPanel(this)) return false; 39 | 40 | if (gameplaySceneSetupData?.beatmapKey.beatmapCharacteristic.containsRotationEvents == true) 41 | { 42 | logger.Logger.Debug("Selected map is 360/90. Disabling the SongProgressPanelModifier"); 43 | CanBeUsedSafely = false; 44 | return false; 45 | } 46 | 47 | foreach (ImageView x in songProgressUIController.GetComponentsInChildren()) 48 | { 49 | if (x.name != "BG") 50 | { 51 | // [0] Fill, [1] BG, [2] Handle 52 | barComponents.Add(x); 53 | } 54 | } 55 | 56 | if (progressConfig.Mode == ProgressConfig.DisplayType.Fixed) 57 | { 58 | barComponents[0].color = progressConfig.Fill; 59 | } 60 | barComponents[1].color = progressConfig.BG.ColorWithAlpha(0.25f); 61 | barComponents[2].color = progressConfig.Handle; 62 | 63 | return true; 64 | } 65 | 66 | public void Update() 67 | { 68 | if (!CanBeUsedSafely) return; 69 | 70 | if (progressConfig.Mode == ProgressConfig.DisplayType.Fade) 71 | { 72 | barComponents[0].color = HSBColor.Lerp( 73 | HSBColor.FromColor(progressConfig.StartColor), 74 | HSBColor.FromColor(progressConfig.EndColor), 75 | audioTimeSyncController.songTime / audioTimeSyncController.songLength) 76 | .ToColor(); 77 | } 78 | } 79 | 80 | protected override void OnDestroy() => base.OnDestroy(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the UITweaks Repository 2 | Are you a player and want to potentially see a feature added into this mod? A developer who found a bug and fixed it? There are multiple ways to contribute your ideas to the UITweaks Repository! 3 | 4 | This project is licensed under the [MIT License](LICENSE). Read the license file to learn more. 5 | 6 | ## For Everyone - [Open an Issue](https://github.com/Exomanz/UITweaks/issues/new) 7 | If you found a bug or have a feature request, please submit an issue. Fill out the form as fully as possible to make it easier to understand and reduce the back-and-forth trying to narrow down what is being discussed: 8 | 9 | - **Title:** Short and to the point, include game and plugin versions if necessary. 10 | - **Description:** As verbose as necessary to explain the issue or feature request. Screenshots and steps to reproduce a bug are always appreciated. 11 | - **Attached Files:** If the issue pertains to a bug, please attach the `_latest.log` file from the Beat Saber directory and any additional screenshots/content you would like to include to aid in tracking down the cause of the problem. These can be videos, configuration files, etc. 12 | 13 | The more complete the issue form is, the faster the bug can be fixed and a patch can be released. 14 | 15 | ## For Developers - Clone UITweaks and [Create a Pull Request](https://github.com/Exomanz/UITweaks/compare) 16 | If you are itching for a feature and want to implement it yourself, or simply have a code suggestion to help make this plugin better, submit a pull request! I would love to incorporate your code into this mod. 17 | 18 | ### Dependencies for Building 19 | - .NET Framework v4.8.0 20 | - BSIPA v4.3.4+ 21 | - SiraUtil v3.10.0+ 22 | - BeatSaberMarkupLanguage v1.11.2+ 23 | - _Heck v1.6.0+* 24 | 25 | **If you are building the project without a local version of Heck installed, build in the `Debug` configuration. If Heck is present, build with `Heck-Debug` to enable features that integrate with Heck's API, such as `SettableSettings`. **You should not be building as `Release`.*** 26 | 27 | In order to build this project locally, clone the source code onto your machine, create the `UITweaks.csproj.user` file, and add your Beat Saber directory path to it. 28 | This file should not be uploaded to GitHub, and is filtered out by the `.gitignore`. 29 | 30 | **UITweaks.csproj.user** 31 | ```xml 32 | 33 | 34 | 35 | 36 | C:\Program Files (x86)\Steam\steamapps\common\Beat Saber 37 | 38 | 39 | ``` 40 | 41 | ### Adding New Dependencies 42 | If you are going to add new dependencies, please ensure that the paths use `$(BeatSaberDir)` in the `UITweaks.csproj` file. This reduces potential merge conflicts and problems when new developers clone the code themselves with dependencies not resolving. 43 | 44 | **UITweaks.csproj** 45 | ```xml 46 | ... 47 | 48 | $(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll 49 | 50 | ... 51 | ``` 52 | -------------------------------------------------------------------------------- /UITweaks/Models/PanelDecoratorBase.cs: -------------------------------------------------------------------------------- 1 | using SiraUtil.Logging; 2 | using System; 3 | using UITweaks.Utilities; 4 | using UnityEngine; 5 | using Zenject; 6 | 7 | namespace UITweaks.Models 8 | { 9 | /// 10 | /// Helper class which contains shared resources for all UITweaks PanelDecorators, as well as helper methods to verify proper setup and disposal. 11 | /// 12 | public abstract class PanelDecoratorBase : MonoBehaviour 13 | { 14 | [Inject] private readonly GameplayCoreSceneSetupData gameplayCoreSceneSetupData; 15 | [Inject] private readonly RainbowEffectManager rainbowEffectManager; 16 | [Inject] protected readonly SiraLog logger; 17 | [Inject] protected readonly CoreGameHUDController gameHUDController; 18 | 19 | /// 20 | /// Determines whether a PanelDecorator is safe to use in the given context. 21 | /// 22 | /// Always evaluates to if the base configuration has the PanelDecorator disabled. 23 | public bool CanBeUsedSafely { get; protected set; } = true; 24 | public Color RainbowColor => rainbowEffectManager.Rainbow; 25 | 26 | public UITweaksConfigBase Config; 27 | public GameObject ParentPanel; 28 | 29 | protected abstract void Init(); 30 | 31 | protected virtual bool ModPanel(in PanelDecoratorBase callingDecorator) 32 | { 33 | string callingTypeName = callingDecorator.GetType().Name; 34 | logger.Logger.Debug($"Setting up new PanelDecorator of type {callingTypeName}"); 35 | 36 | try 37 | { 38 | if (callingDecorator.ParentPanel == null) 39 | throw new NullReferenceException($"Field 'parentPanel' cannot be null when creating an object of type {callingTypeName} (is Counters+ hiding it?)"); 40 | 41 | if (callingDecorator.Config == null) 42 | throw new NullReferenceException($"Field 'config' cannot be null when creating an object of type {callingTypeName}"); 43 | } 44 | catch (NullReferenceException ex) 45 | { 46 | logger.Logger.Error($"PanelDecorator of type {callingTypeName} cannot be properly initialized."); 47 | logger.Logger.Error(ex); 48 | callingDecorator.CanBeUsedSafely = false; 49 | return false; 50 | } 51 | 52 | if (gameplayCoreSceneSetupData.playerSpecificSettings.noTextsAndHuds || gameplayCoreSceneSetupData.gameplayModifiers.zenMode) 53 | { 54 | callingDecorator.CanBeUsedSafely = false; 55 | logger.Logger.Debug($"No Texts/HUDs OR Zen Mode are enabled. {callingTypeName} will not be initialized."); 56 | return false; 57 | } 58 | 59 | if (!callingDecorator.Config.Enabled) 60 | { 61 | callingDecorator.CanBeUsedSafely = false; 62 | logger.Logger.Debug($"PanelDecorator of type {callingTypeName} is disabled and will not be initialized."); 63 | return false; 64 | } 65 | 66 | logger.Logger.Debug($"Successfully initialized new PanelDecorator of type {callingTypeName}"); 67 | callingDecorator.CanBeUsedSafely = true; 68 | return true; 69 | } 70 | 71 | protected virtual void OnDestroy() 72 | { 73 | ParentPanel = null; 74 | Config = null; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /UITweaks/Config/MiscConfig.cs: -------------------------------------------------------------------------------- 1 | using IPA.Config.Stores.Attributes; 2 | using IPA.Config.Stores.Converters; 3 | using UITweaks.Models; 4 | using UnityEngine; 5 | 6 | namespace UITweaks.Config 7 | { 8 | public class MiscConfig : UITweaksConfigBase 9 | { 10 | [Ignore] public override bool Enabled { get; set; } = true; 11 | 12 | /// 13 | /// Controls the speed of the rainbow effect globally. 14 | /// 15 | public virtual float GlobalRainbowSpeed { get; set; } = 0.5f; 16 | 17 | /// 18 | /// If set to , the "Combo" text and number will be italicized. 19 | /// 20 | public virtual bool ItalicizeComboPanel { get; set; } = false; 21 | 22 | /// 23 | /// If set to , the score will be italicized. 24 | /// 25 | public virtual bool ItalicizeScore { get; set; } = false; 26 | 27 | /// 28 | /// If set to , the immediate rank panel will be italicized. 29 | /// 30 | public virtual bool ItalicizeImmediateRank { get; set; } = false; 31 | 32 | /// 33 | /// If set to true, , the rank text (SS, A, etc.) will be colored. 34 | /// 35 | public virtual bool AllowRankColoring { get; set; } = true; 36 | 37 | /// 38 | /// If set to , enables the rainbow effect of the rank text while maintaining an "SS" rank. 39 | /// 40 | public virtual bool RainbowOnSSRank { get; set; } = false; 41 | 42 | /// 43 | /// The of the rank text when the rank is "SS". 44 | /// 45 | [UseConverter(typeof(HexColorConverter))] 46 | public virtual Color RankSSColor { get; set; } = Color.cyan; 47 | 48 | /// 49 | /// The of the rank text when the rank is "S". 50 | /// 51 | [UseConverter(typeof(HexColorConverter))] 52 | public virtual Color RankSColor { get; set; } = Color.green; 53 | 54 | /// 55 | /// The of the rank text when the rank is "A". 56 | /// 57 | [UseConverter(typeof(HexColorConverter))] 58 | public virtual Color RankAColor { get; set; } = Color.yellow; 59 | 60 | /// 61 | /// The of the rank text when the rank is "B". 62 | /// 63 | [UseConverter(typeof(HexColorConverter))] 64 | public virtual Color RankBColor { get; set; } = new Color(1, 0.66f, 0); 65 | 66 | /// 67 | /// The of the rank text when the rank is "C". 68 | /// 69 | [UseConverter(typeof(HexColorConverter))] 70 | public virtual Color RankCColor { get; set; } = new Color(1, 0.33f, 0); 71 | 72 | /// 73 | /// The of the rank text when the rank is "D". 74 | /// 75 | [UseConverter(typeof(HexColorConverter))] 76 | public virtual Color RankDColor { get; set; } = Color.red; 77 | 78 | /// 79 | /// The of the rank text when the rank is "E". 80 | /// 81 | [UseConverter(typeof(HexColorConverter))] 82 | public virtual Color RankEColor { get; set; } = Color.red; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /UITweaks/Decorators/MultiplayerPositionPanelDecorator.cs: -------------------------------------------------------------------------------- 1 | using HMUI; 2 | using System.Collections.Generic; 3 | using TMPro; 4 | using UITweaks.Config; 5 | using UITweaks.Models; 6 | using UnityEngine; 7 | using Zenject; 8 | 9 | namespace UITweaks.PanelModifiers 10 | { 11 | public class MultiplayerPositionPanelDecorator : PanelDecoratorBase 12 | { 13 | [Inject] private readonly PositionConfig positionConfig; 14 | [Inject] private readonly MultiplayerScoreProvider scoreProvider; 15 | 16 | private MultiplayerPositionHUDController positionHUDController; 17 | private Dictionary playerCountToConfigValuesMap; 18 | private MultiplayerScoreProvider.RankedPlayer localConnectedPlayer; 19 | private TextMeshProUGUI playerCountText; 20 | private TextMeshProUGUI dynamicPositionText; 21 | 22 | [Inject] 23 | protected override void Init() 24 | { 25 | positionHUDController = base.gameHUDController.transform.parent.GetComponentInChildren(); 26 | ParentPanel = positionHUDController?.gameObject; 27 | Config = positionConfig; 28 | transform.SetParent(ParentPanel?.transform); 29 | 30 | playerCountToConfigValuesMap = new Dictionary() 31 | { 32 | { 0, Color.white }, 33 | { 1, positionConfig.First }, 34 | { 2, positionConfig.Second }, 35 | { 3, positionConfig.Third }, 36 | { 4, positionConfig.Fourth }, 37 | { 5, positionConfig.Fifth } 38 | }; 39 | 40 | ModPanel(this); 41 | } 42 | 43 | protected override bool ModPanel(in PanelDecoratorBase decorator) 44 | { 45 | if (!base.ModPanel(this)) return false; 46 | 47 | playerCountText = positionHUDController._playerCountText; 48 | dynamicPositionText = positionHUDController._positionText; 49 | 50 | return true; 51 | } 52 | 53 | public void Start() 54 | { 55 | if (!CanBeUsedSafely) return; 56 | 57 | int localPlayerIdx = scoreProvider._rankedPlayers.FindIndex(player => player.isMe); 58 | localConnectedPlayer = scoreProvider.rankedPlayers[localPlayerIdx]; 59 | 60 | dynamicPositionText.color = playerCountToConfigValuesMap[localPlayerIdx + 1]; 61 | playerCountText.color = positionConfig.UseStaticColorForStaticPanel ? positionConfig.StaticPanelColor.ColorWithAlpha(0.25f) : playerCountToConfigValuesMap[scoreProvider.rankedPlayers.Count].ColorWithAlpha(0.25f); 62 | 63 | if (positionConfig.HideFirstPlaceAnimation) 64 | positionHUDController._firstPlayerAnimationGo.transform.Rotate(0, 180, 0); // Text can't render upside-down 65 | } 66 | 67 | public void Update() 68 | { 69 | if (!CanBeUsedSafely) return; 70 | 71 | int position = scoreProvider._rankedPlayers.IndexOf(localConnectedPlayer) + 1; 72 | if (positionHUDController._prevPosition != position) 73 | { 74 | dynamicPositionText.color = playerCountToConfigValuesMap[position]; 75 | 76 | if (!positionConfig.UseStaticColorForStaticPanel) 77 | playerCountText.color = playerCountToConfigValuesMap[position].ColorWithAlpha(0.25f); 78 | } 79 | 80 | if (position == 1 && positionConfig.RainbowOnFirstPlace) 81 | { 82 | dynamicPositionText.color = base.RainbowColor; 83 | if (positionHUDController._firstPlayerAnimationGo?.gameObject != null) 84 | positionHUDController._firstPlayerAnimationGo.GetComponent().color = base.RainbowColor; 85 | 86 | if (!positionConfig.UseStaticColorForStaticPanel) 87 | playerCountText.color = base.RainbowColor.ColorWithAlpha(0.25f); 88 | } 89 | } 90 | 91 | protected override void OnDestroy() 92 | { 93 | base.OnDestroy(); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /UITweaks/Views/Info.bsml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |