├── .gitignore ├── ProgressCounter.sln └── ProgressCounter ├── Counter.cs ├── Dlls └── .gitkeep ├── Plugin.cs ├── ProgressCounter.csproj ├── ProgressUI.cs ├── Properties └── AssemblyInfo.cs ├── ScoreCounter.cs └── Util ├── ReflectionUtil.cs └── SettingsUI.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | 56 | # StyleCop 57 | StyleCopReport.xml 58 | 59 | # Files built by Visual Studio 60 | *_i.c 61 | *_p.c 62 | *_i.h 63 | *.ilk 64 | *.meta 65 | *.obj 66 | *.iobj 67 | *.pch 68 | *.pdb 69 | *.ipdb 70 | *.pgc 71 | *.pgd 72 | *.rsp 73 | *.sbr 74 | *.tlb 75 | *.tli 76 | *.tlh 77 | *.tmp 78 | *.tmp_proj 79 | *.log 80 | *.vspscc 81 | *.vssscc 82 | .builds 83 | *.pidb 84 | *.svclog 85 | *.scc 86 | 87 | # Chutzpah Test files 88 | _Chutzpah* 89 | 90 | # Visual C++ cache files 91 | ipch/ 92 | *.aps 93 | *.ncb 94 | *.opendb 95 | *.opensdf 96 | *.sdf 97 | *.cachefile 98 | *.VC.db 99 | *.VC.VC.opendb 100 | 101 | # Visual Studio profiler 102 | *.psess 103 | *.vsp 104 | *.vspx 105 | *.sap 106 | 107 | # Visual Studio Trace Files 108 | *.e2e 109 | 110 | # TFS 2012 Local Workspace 111 | $tf/ 112 | 113 | # Guidance Automation Toolkit 114 | *.gpState 115 | 116 | # ReSharper is a .NET coding add-in 117 | _ReSharper*/ 118 | *.[Rr]e[Ss]harper 119 | *.DotSettings.user 120 | 121 | # JustCode is a .NET coding add-in 122 | .JustCode 123 | 124 | # TeamCity is a build add-in 125 | _TeamCity* 126 | 127 | # DotCover is a Code Coverage Tool 128 | *.dotCover 129 | 130 | # AxoCover is a Code Coverage Tool 131 | .axoCover/* 132 | !.axoCover/settings.json 133 | 134 | # Visual Studio code coverage results 135 | *.coverage 136 | *.coveragexml 137 | 138 | # NCrunch 139 | _NCrunch_* 140 | .*crunch*.local.xml 141 | nCrunchTemp_* 142 | 143 | # MightyMoose 144 | *.mm.* 145 | AutoTest.Net/ 146 | 147 | # Web workbench (sass) 148 | .sass-cache/ 149 | 150 | # Installshield output folder 151 | [Ee]xpress/ 152 | 153 | # DocProject is a documentation generator add-in 154 | DocProject/buildhelp/ 155 | DocProject/Help/*.HxT 156 | DocProject/Help/*.HxC 157 | DocProject/Help/*.hhc 158 | DocProject/Help/*.hhk 159 | DocProject/Help/*.hhp 160 | DocProject/Help/Html2 161 | DocProject/Help/html 162 | 163 | # Click-Once directory 164 | publish/ 165 | 166 | # Publish Web Output 167 | *.[Pp]ublish.xml 168 | *.azurePubxml 169 | # Note: Comment the next line if you want to checkin your web deploy settings, 170 | # but database connection strings (with potential passwords) will be unencrypted 171 | *.pubxml 172 | *.publishproj 173 | 174 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 175 | # checkin your Azure Web App publish settings, but sensitive information contained 176 | # in these scripts will be unencrypted 177 | PublishScripts/ 178 | 179 | # NuGet Packages 180 | *.nupkg 181 | # The packages folder can be ignored because of Package Restore 182 | **/[Pp]ackages/* 183 | # except build/, which is used as an MSBuild target. 184 | !**/[Pp]ackages/build/ 185 | # Uncomment if necessary however generally it will be regenerated when needed 186 | #!**/[Pp]ackages/repositories.config 187 | # NuGet v3's project.json files produces more ignorable files 188 | *.nuget.props 189 | *.nuget.targets 190 | 191 | # Microsoft Azure Build Output 192 | csx/ 193 | *.build.csdef 194 | 195 | # Microsoft Azure Emulator 196 | ecf/ 197 | rcf/ 198 | 199 | # Windows Store app package directories and files 200 | AppPackages/ 201 | BundleArtifacts/ 202 | Package.StoreAssociation.xml 203 | _pkginfo.txt 204 | *.appx 205 | 206 | # Visual Studio cache files 207 | # files ending in .cache can be ignored 208 | *.[Cc]ache 209 | # but keep track of directories ending in .cache 210 | !*.[Cc]ache/ 211 | 212 | # Others 213 | ClientBin/ 214 | ~$* 215 | *~ 216 | *.dbmdl 217 | *.dbproj.schemaview 218 | *.jfm 219 | *.pfx 220 | *.publishsettings 221 | orleans.codegen.cs 222 | 223 | # Including strong name files can present a security risk 224 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 225 | #*.snk 226 | 227 | # Since there are multiple workflows, uncomment next line to ignore bower_components 228 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 229 | #bower_components/ 230 | 231 | # RIA/Silverlight projects 232 | Generated_Code/ 233 | 234 | # Backup & report files from converting an old project file 235 | # to a newer Visual Studio version. Backup files are not needed, 236 | # because we have git ;-) 237 | _UpgradeReport_Files/ 238 | Backup*/ 239 | UpgradeLog*.XML 240 | UpgradeLog*.htm 241 | ServiceFabricBackup/ 242 | *.rptproj.bak 243 | 244 | # SQL Server files 245 | *.mdf 246 | *.ldf 247 | *.ndf 248 | 249 | # Business Intelligence projects 250 | *.rdl.data 251 | *.bim.layout 252 | *.bim_*.settings 253 | *.rptproj.rsuser 254 | 255 | # Microsoft Fakes 256 | FakesAssemblies/ 257 | 258 | # GhostDoc plugin setting file 259 | *.GhostDoc.xml 260 | 261 | # Node.js Tools for Visual Studio 262 | .ntvs_analysis.dat 263 | node_modules/ 264 | 265 | # Visual Studio 6 build log 266 | *.plg 267 | 268 | # Visual Studio 6 workspace options file 269 | *.opt 270 | 271 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 272 | *.vbw 273 | 274 | # Visual Studio LightSwitch build output 275 | **/*.HTMLClient/GeneratedArtifacts 276 | **/*.DesktopClient/GeneratedArtifacts 277 | **/*.DesktopClient/ModelManifest.xml 278 | **/*.Server/GeneratedArtifacts 279 | **/*.Server/ModelManifest.xml 280 | _Pvt_Extensions 281 | 282 | # Paket dependency manager 283 | .paket/paket.exe 284 | paket-files/ 285 | 286 | # FAKE - F# Make 287 | .fake/ 288 | 289 | # JetBrains Rider 290 | .idea/ 291 | *.sln.iml 292 | 293 | # CodeRush 294 | .cr/ 295 | 296 | # Python Tools for Visual Studio (PTVS) 297 | __pycache__/ 298 | *.pyc 299 | 300 | # Cake - Uncomment if you are using it 301 | # tools/** 302 | # !tools/packages.config 303 | 304 | # Tabs Studio 305 | *.tss 306 | 307 | # Telerik's JustMock configuration file 308 | *.jmconfig 309 | 310 | # BizTalk build output 311 | *.btp.cs 312 | *.btm.cs 313 | *.odx.cs 314 | *.xsd.cs 315 | 316 | # OpenCover UI analysis results 317 | OpenCover/ 318 | 319 | # Azure Stream Analytics local run output 320 | ASALocalRun/ 321 | 322 | # MSBuild Binary and Structured Log 323 | *.binlog 324 | 325 | # NVidia Nsight GPU debugger configuration file 326 | *.nvuser 327 | 328 | # MFractors (Xamarin productivity tool) working folder 329 | .mfractor/ 330 | *.dll 331 | -------------------------------------------------------------------------------- /ProgressCounter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2018 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProgressCounter", "ProgressCounter\ProgressCounter.csproj", "{B094BF9C-E252-463C-B5A3-2A94E1D228E7}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {B094BF9C-E252-463C-B5A3-2A94E1D228E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {B094BF9C-E252-463C-B5A3-2A94E1D228E7}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {B094BF9C-E252-463C-B5A3-2A94E1D228E7}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {B094BF9C-E252-463C-B5A3-2A94E1D228E7}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3AE2912B-59A2-4068-849B-37AFC4DC627C} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /ProgressCounter/Counter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using UnityEngine; 8 | using TMPro; 9 | using UnityEngine.UI; 10 | using System.Reflection; 11 | 12 | namespace ProgressCounter 13 | { 14 | public class Counter : MonoBehaviour 15 | { 16 | 17 | TextMeshPro _timeMesh; 18 | AudioTimeSyncController _audioTimeSync; 19 | Image _image; 20 | 21 | bool useTimeLeft = false; 22 | 23 | IEnumerator WaitForLoad() 24 | { 25 | bool loaded = false; 26 | while (!loaded) 27 | { 28 | _audioTimeSync = Resources.FindObjectsOfTypeAll().FirstOrDefault(); 29 | 30 | if (_audioTimeSync == null) 31 | yield return new WaitForSeconds(0.01f); 32 | else 33 | loaded = true; 34 | } 35 | 36 | Init(); 37 | } 38 | 39 | private void Awake() 40 | { 41 | StartCoroutine(WaitForLoad()); 42 | } 43 | 44 | void Init() 45 | { 46 | _timeMesh = this.gameObject.AddComponent(); 47 | _timeMesh.text = "0:00"; 48 | _timeMesh.fontSize = 4; 49 | _timeMesh.color = Color.white; 50 | _timeMesh.font = Resources.Load("Teko-Medium SDF No Glow"); 51 | _timeMesh.GetComponent().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 1f); 52 | _timeMesh.GetComponent().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 1f); 53 | _timeMesh.rectTransform.position = Plugin.progressCounterPosition; 54 | 55 | var image = ReflectionUtil.GetPrivateField( 56 | Resources.FindObjectsOfTypeAll().First(), "_multiplierProgressImage"); 57 | 58 | GameObject g = new GameObject(); 59 | Canvas canvas = g.AddComponent(); 60 | canvas.renderMode = RenderMode.WorldSpace; 61 | CanvasScaler cs = g.AddComponent(); 62 | cs.scaleFactor = 10.0f; 63 | cs.dynamicPixelsPerUnit = 10f; 64 | GraphicRaycaster gr = g.AddComponent(); 65 | g.GetComponent().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 1f); 66 | g.GetComponent().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 1f); 67 | 68 | GameObject g2 = new GameObject(); 69 | _image = g2.AddComponent(); 70 | g2.transform.parent = g.transform; 71 | g2.GetComponent().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 0.5f); 72 | g2.GetComponent().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0.5f); 73 | g2.transform.localScale = new Vector3(2.3f, 2.3f, 2.3f); 74 | 75 | _image.sprite = image.sprite; 76 | _image.type = Image.Type.Filled; 77 | _image.fillMethod = Image.FillMethod.Radial360; 78 | _image.fillOrigin = (int)Image.Origin360.Top; 79 | _image.fillClockwise = true; 80 | 81 | 82 | GameObject g3 = new GameObject(); 83 | var bg = g3.AddComponent(); 84 | g3.transform.parent = g.transform; 85 | g3.GetComponent().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 0.5f); 86 | g3.GetComponent().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0.5f); 87 | g3.transform.localScale = new Vector3(2.3f, 2.3f, 2.3f); 88 | 89 | bg.sprite = image.sprite; 90 | bg.CrossFadeAlpha(0.05f, 1f, false); 91 | 92 | g.GetComponent().SetParent(this.transform, false); 93 | g.transform.localPosition = new Vector3(-0.25f, .25f, 0f); 94 | 95 | useTimeLeft = Plugin.progressTimeLeft; 96 | } 97 | 98 | void Update() 99 | { 100 | if (_audioTimeSync == false) 101 | { 102 | _audioTimeSync = Resources.FindObjectsOfTypeAll().FirstOrDefault(); 103 | return; 104 | } 105 | 106 | var time = 0f; 107 | if (useTimeLeft) 108 | time = _audioTimeSync.songLength - _audioTimeSync.songTime; 109 | else 110 | time = _audioTimeSync.songTime; 111 | 112 | if (time <= 0f) 113 | return; 114 | 115 | _timeMesh.text = $"{Math.Floor(time / 60):N0}:{Math.Floor(time % 60):00}"; 116 | _image.fillAmount = _audioTimeSync.songTime / _audioTimeSync.songLength; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /ProgressCounter/Dlls/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Strackeror/BeatSaberProgressCounter/31c65399fb64f324ab5f01faa0f4e961b1fb95b5/ProgressCounter/Dlls/.gitkeep -------------------------------------------------------------------------------- /ProgressCounter/Plugin.cs: -------------------------------------------------------------------------------- 1 | using IllusionPlugin; 2 | using System; 3 | using System.Globalization; 4 | using System.Linq; 5 | using UnityEngine; 6 | using UnityEngine.SceneManagement; 7 | 8 | namespace ProgressCounter 9 | { 10 | public class Plugin : IPlugin 11 | { 12 | public string Name => "ProgressCounter"; 13 | public string Version => "4.0"; 14 | 15 | private readonly string[] env = { "DefaultEnvironment", "BigMirrorEnvironment", "TriangleEnvironment", "NiceEnvironment" }; 16 | private bool _init = false; 17 | 18 | public static bool progressTimeLeft = false; 19 | public static Vector3 scoreCounterPosition = new Vector3(3.25f, 0.5f, 7f); 20 | public static Vector3 progressCounterPosition = new Vector3(0.25f, -2f, 7.5f); 21 | 22 | public static int progressCounterDecimalPrecision; 23 | 24 | public void OnApplicationQuit() 25 | { 26 | SceneManager.activeSceneChanged -= OnSceneChanged; 27 | } 28 | 29 | private string FormatVector(Vector3 v) 30 | { 31 | return FormattableString.Invariant($"{v.x:0.00},{v.y:0.00},{v.z:0.00}"); 32 | } 33 | 34 | private Vector3 ReadVector(string s) 35 | { 36 | var arr = s.Split(',').Select(f => float.Parse(f, CultureInfo.InvariantCulture)).ToArray(); 37 | return new Vector3(arr[0], arr[1], arr[2]); 38 | } 39 | 40 | public void OnApplicationStart() 41 | { 42 | if (_init) return; 43 | _init = true; 44 | 45 | scoreCounterPosition = ReadVector(ModPrefs.GetString("BeatSaberProgressCounter", "scorePosition", 46 | FormatVector(scoreCounterPosition), true)); 47 | progressCounterPosition = ReadVector(ModPrefs.GetString("BeatSaberProgressCounter", "progressPosition", 48 | FormatVector(progressCounterPosition), true)); 49 | 50 | progressTimeLeft = ModPrefs.GetBool("BeatSaberProgressCounter", "progressTimeLeft", false, true); 51 | progressCounterDecimalPrecision = ModPrefs.GetInt("BeatSaberProgressCounter", "progressCounterDecimalPrecision", 1, true); 52 | 53 | SceneManager.activeSceneChanged += OnSceneChanged; 54 | } 55 | 56 | private void OnSceneChanged(Scene _, Scene scene) 57 | { 58 | if (scene.name == "Menu") 59 | { 60 | ProgressUI.CreateSettingsUI(); 61 | } 62 | 63 | if (env.Contains(scene.name)) 64 | { 65 | new GameObject("Counter").AddComponent(); 66 | new GameObject("ScoreCounter").AddComponent(); 67 | } 68 | } 69 | 70 | public void OnFixedUpdate() 71 | { 72 | } 73 | 74 | public void OnLevelWasInitialized(int level) 75 | { 76 | } 77 | 78 | public void OnLevelWasLoaded(int level) 79 | { 80 | } 81 | 82 | public void OnUpdate() 83 | { 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /ProgressCounter/ProgressCounter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {B094BF9C-E252-463C-B5A3-2A94E1D228E7} 8 | Library 9 | Properties 10 | ProgressCounter 11 | ProgressCounter 12 | v4.6.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | False 35 | Dlls\Assembly-CSharp.dll 36 | 37 | 38 | False 39 | Dlls\IllusionPlugin.dll 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | False 51 | Dlls\TextMeshPro-1.0.55.2017.1.0b12.dll 52 | 53 | 54 | False 55 | Dlls\UnityEngine.dll 56 | 57 | 58 | False 59 | Dlls\UnityEngine.CoreModule.dll 60 | 61 | 62 | False 63 | Dlls\UnityEngine.TextRenderingModule.dll 64 | 65 | 66 | False 67 | Dlls\UnityEngine.UI.dll 68 | 69 | 70 | False 71 | Dlls\UnityEngine.UIModule.dll 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | xcopy /Y /D $(TargetPath) D:\Apps\Oculus\Software\hyperbolic-magnetism-beat-saber\Plugins\ 102 | 103 | -------------------------------------------------------------------------------- /ProgressCounter/ProgressUI.cs: -------------------------------------------------------------------------------- 1 | using IllusionPlugin; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using UnityEngine; 6 | 7 | namespace ProgressCounter 8 | { 9 | internal class ProgressUI : MonoBehaviour 10 | { 11 | //Necessary because monobehaviours can't be generics 12 | public class DecimalSettingsViewController : ListViewController { } 13 | public class PositionSettingsViewController : ListViewController> { } 14 | 15 | public static void CreateSettingsUI() 16 | { 17 | var subMenu = SettingsUI.CreateSubMenu("Progress Counter"); 18 | 19 | //Time Left Bool 20 | { 21 | var timeLeft = subMenu.AddBool("Time Left"); 22 | 23 | timeLeft.GetValue = () => Plugin.progressTimeLeft; 24 | 25 | timeLeft.SetValue = (bool value) => 26 | { 27 | Plugin.progressTimeLeft = value; 28 | ModPrefs.SetBool("BeatSaberProgressCounter", "progressTimeLeft", Plugin.progressTimeLeft); 29 | }; 30 | } 31 | 32 | 33 | //Decimal Precision 34 | { 35 | int[] precisionValues = { 1, 2, 3, 4 }; 36 | var precisionMenu = subMenu.AddListSetting("Decimal Precision"); 37 | precisionMenu.values = precisionValues.ToList(); 38 | 39 | precisionMenu.GetValue = () => Plugin.progressCounterDecimalPrecision; 40 | 41 | precisionMenu.SetValue = (int value) => 42 | { 43 | Plugin.progressCounterDecimalPrecision = (int)value; 44 | 45 | ModPrefs.SetFloat("BeatSaberProgressCounter", "progressCounterDecimalPrecision", value); 46 | }; 47 | 48 | precisionMenu.GetTextForValue = (int value) => value + " Place" + ((value == 1) ? "s" : ""); 49 | 50 | 51 | } 52 | 53 | // Score Counter Position Preset 54 | { 55 | var scorePositions = new List> 56 | { 57 | {Plugin.scoreCounterPosition, "Current"}, 58 | {new Vector3(3.25f, 0.5f, 7.0f), "Default" }, 59 | {new Vector3(-3.25f, -0.3f, 7.0f), "Left" }, 60 | }; 61 | 62 | var scorePositionPresetMenu = subMenu.AddListSetting("Score Counter Position"); 63 | scorePositionPresetMenu.values = scorePositions; 64 | 65 | scorePositionPresetMenu.GetValue = () => scorePositions[0]; 66 | scorePositionPresetMenu.GetTextForValue = (value) => value.Item2; 67 | scorePositionPresetMenu.SetValue = (v) => 68 | { 69 | Plugin.scoreCounterPosition = v.Item1; 70 | ModPrefs.SetString("BeatSaberProgressCounter", "scorePosition", 71 | FormattableString.Invariant( 72 | $"{Plugin.scoreCounterPosition.x:0.00},{Plugin.scoreCounterPosition.y:0.00},{Plugin.scoreCounterPosition.z:0.00}")); 73 | }; 74 | } 75 | 76 | //Timer Position Preset 77 | { 78 | var timerPositions = new List> 79 | { 80 | {Plugin.progressCounterPosition, "Current"}, 81 | {new Vector3(.25f, -2.0f, 7.5f), "Default"}, 82 | {new Vector3(.25f, 3.4f, 7.5f), "Top"}, 83 | {new Vector3(-3.0f, 3.4f, 7f), "Top Left"}, 84 | {new Vector3(3.5f, 3.4f, 7f), "Top Right"}, 85 | {new Vector3(-3.0f, -1.75f, 7f), "Bottom Left"}, 86 | {new Vector3(3.5f, -1.6f, 7f), "Bottom Right"}, 87 | }; 88 | 89 | var timerPositionMenu = subMenu.AddListSetting("Timer Position"); 90 | timerPositionMenu.values = timerPositions; 91 | 92 | timerPositionMenu.GetValue = () => timerPositions[0]; 93 | timerPositionMenu.GetTextForValue = (value) => value.Item2; 94 | timerPositionMenu.SetValue = (v) => 95 | { 96 | Plugin.progressCounterPosition = v.Item1; 97 | ModPrefs.SetString("BeatSaberProgressCounter", "progressPosition", 98 | FormattableString.Invariant( 99 | $"{Plugin.progressCounterPosition.x:0.00},{Plugin.progressCounterPosition.y:0.00},{Plugin.progressCounterPosition.z:0.00}")); 100 | }; 101 | } 102 | 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /ProgressCounter/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ProgressCounter")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ProgressCounter")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b094bf9c-e252-463c-b5a3-2a94e1d228e7")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ProgressCounter/ScoreCounter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using UnityEngine; 8 | using TMPro; 9 | 10 | namespace ProgressCounter 11 | { 12 | public class ScoreCounter : MonoBehaviour 13 | { 14 | TextMeshPro _scoreMesh; 15 | ScoreController _scoreController; 16 | BeatmapObjectExecutionRatingsRecorder _objectRatingRecorder; 17 | 18 | GameObject _RankObject; 19 | TextMeshPro _RankText; 20 | 21 | int _maxPossibleScore = 0; 22 | 23 | IEnumerator WaitForLoad() 24 | { 25 | bool loaded = false; 26 | while (!loaded) 27 | { 28 | _scoreController = Resources.FindObjectsOfTypeAll().FirstOrDefault(); 29 | _objectRatingRecorder = FindObjectOfType(); 30 | 31 | if (_scoreController == null || _objectRatingRecorder == null) 32 | yield return new WaitForSeconds(0.1f); 33 | else 34 | loaded = true; 35 | } 36 | 37 | Init(); 38 | } 39 | 40 | void Awake() 41 | { 42 | StartCoroutine(WaitForLoad()); 43 | } 44 | 45 | private void Init() 46 | { 47 | _scoreMesh = this.gameObject.AddComponent(); 48 | _scoreMesh.text = "100.0%"; 49 | _scoreMesh.fontSize = 3; 50 | _scoreMesh.color = Color.white; 51 | _scoreMesh.font = Resources.Load("Teko-Medium SDF No Glow"); 52 | _scoreMesh.alignment = TextAlignmentOptions.Center; 53 | _scoreMesh.rectTransform.position = Plugin.scoreCounterPosition; 54 | 55 | _RankObject = new GameObject(); 56 | _RankText = _RankObject.AddComponent(); 57 | _RankText.text = "SSS"; 58 | _RankText.fontSize = 4; 59 | _RankText.color = Color.white; 60 | _RankText.font = Resources.Load("Teko-Medium SDF No Glow"); 61 | _RankText.alignment = TextAlignmentOptions.Center; 62 | _RankText.rectTransform.position = _scoreMesh.rectTransform.position + new Vector3(0f, -0.4f, 0f); 63 | 64 | if (_scoreController != null) 65 | _scoreController.scoreDidChangeEvent += UpdateScore; 66 | } 67 | 68 | public string GetRank(int score, float prec) 69 | { 70 | if (score >= _maxPossibleScore) 71 | { 72 | return "SSS"; 73 | } 74 | if (prec > 0.9f) 75 | { 76 | return "SS"; 77 | } 78 | if (prec > 0.8f) 79 | { 80 | return "S"; 81 | } 82 | if (prec > 0.65f) 83 | { 84 | return "A"; 85 | } 86 | if (prec > 0.5f) 87 | { 88 | return "B"; 89 | } 90 | if (prec > 0.35f) 91 | { 92 | return "C"; 93 | } 94 | if (prec > 0.2f) 95 | { 96 | return "D"; 97 | } 98 | return "E"; 99 | } 100 | 101 | public void UpdateScore(int score) 102 | { 103 | 104 | if (_objectRatingRecorder != null) 105 | { 106 | List _ratings = ReflectionUtil.GetPrivateField>(_objectRatingRecorder, "_beatmapObjectExecutionRatings"); 107 | if (_ratings != null) 108 | { 109 | int notes = 0; 110 | foreach (BeatmapObjectExecutionRating rating in _ratings) 111 | { 112 | if (rating.beatmapObjectRatingType == BeatmapObjectExecutionRating.BeatmapObjectExecutionRatingType.Note) 113 | notes++; 114 | } 115 | _maxPossibleScore = ScoreController.MaxScoreForNumberOfNotes(notes); 116 | } 117 | } 118 | 119 | if (_scoreMesh != null) 120 | { 121 | if (_maxPossibleScore == 0) 122 | { 123 | _scoreMesh.text = "100.0%"; 124 | _RankText.text = "SSS"; 125 | } 126 | else 127 | { 128 | float ratio = score / (float) _maxPossibleScore; 129 | 130 | _scoreMesh.text = (Mathf.Clamp(ratio, 0.0f, 1.0f) * 100.0f).ToString("F" + Plugin.progressCounterDecimalPrecision) + "%"; 131 | _RankText.text = GetRank(score, ratio); 132 | } 133 | 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /ProgressCounter/Util/ReflectionUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using UnityEngine; 7 | namespace ProgressCounter 8 | { 9 | 10 | public static class ReflectionUtil 11 | { 12 | public static void SetPrivateField(this object obj, string fieldName, object value) 13 | { 14 | obj.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).SetValue(obj, value); 15 | } 16 | 17 | public static T GetPrivateField(this object obj, string fieldName) 18 | { 19 | return (T)((object)obj.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj)); 20 | } 21 | 22 | public static void SetPrivateProperty(this object obj, string propertyName, object value) 23 | { 24 | obj.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).SetValue(obj, value, null); 25 | } 26 | 27 | public static void InvokePrivateMethod(this object obj, string methodName, object[] methodParams) 28 | { 29 | obj.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic).Invoke(obj, methodParams); 30 | } 31 | public static Component CopyComponent(Component original, Type originalType, Type overridingType, GameObject destination) 32 | { 33 | var copy = destination.AddComponent(overridingType); 34 | 35 | Type type = originalType; 36 | while (type != typeof(MonoBehaviour)) 37 | { 38 | CopyForType(type, original, copy); 39 | type = type.BaseType; 40 | } 41 | 42 | return copy; 43 | } 44 | 45 | private static void CopyForType(Type type, Component source, Component destination) 46 | { 47 | FieldInfo[] myObjectFields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetField); 48 | 49 | foreach (FieldInfo fi in myObjectFields) 50 | { 51 | fi.SetValue(destination, fi.GetValue(source)); 52 | } 53 | } 54 | } 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /ProgressCounter/Util/SettingsUI.cs: -------------------------------------------------------------------------------- 1 | #if !NewUI 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using TMPro; 7 | using UnityEngine; 8 | using UnityEngine.SceneManagement; 9 | using VRUI; 10 | 11 | namespace ProgressCounter 12 | { 13 | public class ListViewController : ListSettingsController 14 | { 15 | public Func GetValue = () => default(T); 16 | public Action SetValue = (_) => { }; 17 | public Func GetTextForValue = (_) => "?"; 18 | 19 | public List values; 20 | 21 | protected override void GetInitValues(out int idx, out int numberOfElements) 22 | { 23 | numberOfElements = values.Count; 24 | var value = GetValue(); 25 | 26 | numberOfElements = values.Count(); 27 | idx = values.FindIndex(v => v.Equals(value)); 28 | } 29 | 30 | protected override void ApplyValue(int idx) 31 | { 32 | SetValue(values[idx]); 33 | } 34 | 35 | protected override string TextForValue(int idx) 36 | { 37 | return GetTextForValue(values[idx]); 38 | } 39 | } 40 | 41 | public class BoolViewController : SwitchSettingsController 42 | { 43 | public Func GetValue = () => false; 44 | public Action SetValue = (a) => { }; 45 | 46 | protected override bool GetInitValue() 47 | { 48 | return (GetValue()); 49 | } 50 | 51 | protected override void ApplyValue(bool value) 52 | { 53 | SetValue(value); 54 | } 55 | 56 | protected override string TextForValue(bool value) 57 | { 58 | return (value) ? "ON" : "OFF"; 59 | } 60 | } 61 | 62 | public class SubMenu 63 | { 64 | public Transform transform; 65 | 66 | public SubMenu(Transform transform) 67 | { 68 | this.transform = transform; 69 | } 70 | 71 | public BoolViewController AddBool(string name) 72 | { 73 | return AddToggleSetting(name); 74 | } 75 | 76 | public T AddListSetting(string name) where T : ListSettingsController 77 | { 78 | var volumeSettings = Resources.FindObjectsOfTypeAll().FirstOrDefault(); 79 | GameObject newSettingsObject = MonoBehaviour.Instantiate(volumeSettings.gameObject, transform); 80 | 81 | var volume = newSettingsObject.GetComponent(); 82 | T newSettingsController = (T)ReflectionUtil.CopyComponent(volume, typeof(ListSettingsController), typeof(T), newSettingsObject); 83 | MonoBehaviour.DestroyImmediate(volume); 84 | 85 | newSettingsObject.name = name; 86 | newSettingsObject.GetComponentInChildren().text = name; 87 | return newSettingsController; 88 | } 89 | 90 | 91 | public T AddToggleSetting(string name) where T : SwitchSettingsController 92 | { 93 | var volumeSettings = Resources.FindObjectsOfTypeAll().FirstOrDefault(); 94 | GameObject newSettingsObject = MonoBehaviour.Instantiate(volumeSettings.gameObject, transform); 95 | newSettingsObject.name = name; 96 | 97 | WindowModeSettingsController volume = newSettingsObject.GetComponent(); 98 | T newToggleSettingsController = (T)ReflectionUtil.CopyComponent(volume, typeof(SwitchSettingsController), typeof(T), newSettingsObject); 99 | MonoBehaviour.DestroyImmediate(volume); 100 | 101 | newSettingsObject.GetComponentInChildren().text = name; 102 | 103 | return newToggleSettingsController; 104 | } 105 | } 106 | 107 | public class SettingsUI : MonoBehaviour 108 | { 109 | public static SettingsUI Instance = null; 110 | private MainMenuViewController _mainMenuViewController = null; 111 | private SimpleDialogPromptViewController prompt = null; 112 | 113 | private static MainSettingsTableCell tableCell = null; 114 | 115 | public static void OnLoad() 116 | { 117 | if (Instance != null) return; 118 | new GameObject("SettingsUI").AddComponent(); 119 | } 120 | 121 | public static bool IsMenuScene(Scene scene) 122 | { 123 | return (scene.name == "Menu"); 124 | } 125 | 126 | public static bool IsGameScene(Scene scene) 127 | { 128 | return (scene.name == "StandardLevel"); 129 | } 130 | 131 | public void Awake() 132 | { 133 | if (Instance == null) 134 | { 135 | Instance = this; 136 | SceneManager.activeSceneChanged += SceneManagerOnActiveSceneChanged; 137 | DontDestroyOnLoad(gameObject); 138 | } 139 | else 140 | { 141 | Destroy(this); 142 | } 143 | } 144 | 145 | public void SceneManagerOnActiveSceneChanged(Scene arg0, Scene scene) 146 | { 147 | if (IsMenuScene(scene)) 148 | { 149 | _mainMenuViewController = Resources.FindObjectsOfTypeAll().First(); 150 | var _menuMasterViewController = Resources.FindObjectsOfTypeAll().First(); 151 | prompt = ReflectionUtil.GetPrivateField(_menuMasterViewController, "_simpleDialogPromptViewController"); 152 | } 153 | } 154 | 155 | public static void LogComponents(Transform t, string prefix = "=", bool includeScipts = false) 156 | { 157 | Console.WriteLine(prefix + ">" + t.name); 158 | 159 | if (includeScipts) 160 | { 161 | foreach (var comp in t.GetComponents()) 162 | { 163 | Console.WriteLine(prefix + "-->" + comp.GetType()); 164 | } 165 | } 166 | 167 | foreach (Transform child in t) 168 | { 169 | LogComponents(child, prefix + "=", includeScipts); 170 | } 171 | } 172 | 173 | public static SubMenu CreateSubMenu(string name) 174 | { 175 | if (!IsMenuScene(SceneManager.GetActiveScene())) 176 | { 177 | Console.WriteLine("Cannot create settings menu when no in the main scene."); 178 | return null; 179 | } 180 | 181 | if (tableCell == null) 182 | { 183 | tableCell = Resources.FindObjectsOfTypeAll().FirstOrDefault(); 184 | // Get a refence to the Settings Table cell text in case we want to change fint size, etc 185 | var text = tableCell.GetPrivateField("_settingsSubMenuText"); 186 | } 187 | 188 | var temp = Resources.FindObjectsOfTypeAll().FirstOrDefault(); 189 | 190 | var others = temp.transform.Find("Others"); 191 | var tweakSettingsObject = Instantiate(others.gameObject, others.transform.parent); 192 | Transform mainContainer = CleanScreen(tweakSettingsObject.transform); 193 | 194 | var tweaksSubMenu = new SettingsSubMenuInfo(); 195 | tweaksSubMenu.SetPrivateField("_menuName", name); 196 | tweaksSubMenu.SetPrivateField("_controller", tweakSettingsObject.GetComponent()); 197 | 198 | var mainSettingsMenu = Resources.FindObjectsOfTypeAll().FirstOrDefault(); 199 | var subMenus = mainSettingsMenu.GetPrivateField("_settingsSubMenuInfos").ToList(); 200 | subMenus.Add(tweaksSubMenu); 201 | mainSettingsMenu.SetPrivateField("_settingsSubMenuInfos", subMenus.ToArray()); 202 | 203 | SubMenu menu = new SubMenu(mainContainer); 204 | return menu; 205 | } 206 | 207 | private static Transform CleanScreen(Transform screen) 208 | { 209 | var container = screen.Find("Content").Find("SettingsContainer"); 210 | var tempList = container.Cast().ToList(); 211 | foreach (var child in tempList) 212 | { 213 | DestroyImmediate(child.gameObject); 214 | } 215 | return container; 216 | } 217 | } 218 | } 219 | 220 | #endif --------------------------------------------------------------------------------