├── .gitignore
├── Images
└── screenshot.jpg
├── SliceDetails
├── Resources
│ ├── bloq.png
│ ├── dot.png
│ ├── arrow.png
│ ├── cut_arrow.png
│ └── bloq_gradient.png
├── Directory.Build.props
├── Installers
│ ├── SDAppInstaller.cs
│ ├── SDGameInstaller.cs
│ └── SDMenuInstaller.cs
├── manifest.json
├── AffinityPatches
│ ├── ResultsViewControllerPatch.cs
│ ├── MenuTransitionsHelperPatch.cs
│ ├── SoloFreePlayFlowCoordinatorPatch.cs
│ ├── PartyFreePlayFlowCoordinatorPatch.cs
│ └── PauseMenuManagerPatches.cs
├── UI
│ ├── HoverHintControllerGrabber.cs
│ ├── HoverHintControllerHandler.cs
│ ├── AssetLoader.cs
│ ├── Views
│ │ ├── settingsView.bsml
│ │ └── gridView.bsml
│ ├── SettingsViewController.cs
│ ├── PauseUIController.cs
│ ├── SelectedTileIndicator.cs
│ ├── UICreator.cs
│ ├── NoteUI.cs
│ └── GridViewController.cs
├── Utils
│ └── ColorSchemeManager.cs
├── Data
│ ├── NoteInfo.cs
│ ├── Score.cs
│ └── Tile.cs
├── Plugin.cs
├── Settings
│ └── SettingsStore.cs
├── SliceProcessor.cs
├── SliceRecorder.cs
└── SliceDetails.csproj
├── README.md
├── LICENSE
└── SliceDetails.sln
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | */bin
3 | */obj
4 | *.csproj.user
--------------------------------------------------------------------------------
/Images/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckosmic/SliceDetails/HEAD/Images/screenshot.jpg
--------------------------------------------------------------------------------
/SliceDetails/Resources/bloq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckosmic/SliceDetails/HEAD/SliceDetails/Resources/bloq.png
--------------------------------------------------------------------------------
/SliceDetails/Resources/dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckosmic/SliceDetails/HEAD/SliceDetails/Resources/dot.png
--------------------------------------------------------------------------------
/SliceDetails/Resources/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckosmic/SliceDetails/HEAD/SliceDetails/Resources/arrow.png
--------------------------------------------------------------------------------
/SliceDetails/Resources/cut_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckosmic/SliceDetails/HEAD/SliceDetails/Resources/cut_arrow.png
--------------------------------------------------------------------------------
/SliceDetails/Resources/bloq_gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckosmic/SliceDetails/HEAD/SliceDetails/Resources/bloq_gradient.png
--------------------------------------------------------------------------------
/SliceDetails/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | True
6 | BSIPA
7 |
8 |
--------------------------------------------------------------------------------
/SliceDetails/Installers/SDAppInstaller.cs:
--------------------------------------------------------------------------------
1 | using SliceDetails.UI;
2 | using Zenject;
3 |
4 | namespace SliceDetails.Installers
5 | {
6 | public class SDAppInstaller : Installer
7 | {
8 | public override void InstallBindings() {
9 | Container.Bind().AsSingle().Lazy();
10 | Container.Bind().AsSingle();
11 | Container.Bind().AsSingle();
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/SliceDetails/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json",
3 | "id": "SliceDetails",
4 | "name": "SliceDetails",
5 | "author": "ckosmic",
6 | "version": "1.3.1",
7 | "description": "View your average cuts per-angle per-grid position in the pause menu and completion screen.",
8 | "gameVersion": "1.20.0",
9 | "dependsOn": {
10 | "BSIPA": "^4.2.1",
11 | "BeatSaberMarkupLanguage": "^1.6.0",
12 | "SiraUtil": "^3.0.0"
13 | },
14 | "links": {
15 | "project-source": "https://github.com/ckosmic/SliceDetails"
16 | }
17 | }
--------------------------------------------------------------------------------
/SliceDetails/AffinityPatches/ResultsViewControllerPatch.cs:
--------------------------------------------------------------------------------
1 | using SiraUtil.Affinity;
2 | using SliceDetails.UI;
3 |
4 | namespace SliceDetails.AffinityPatches
5 | {
6 | internal class ResultsViewControllerPatch : IAffinity
7 | {
8 | private readonly UICreator _uiCreator;
9 |
10 | public ResultsViewControllerPatch(UICreator uiCreator) {
11 | _uiCreator = uiCreator;
12 | }
13 |
14 | [AffinityPostfix]
15 | [AffinityPatch(typeof(ResultsViewController), nameof(ResultsViewController.DisableResultEnvironmentController))]
16 | internal void Postfix(ref ResultsViewController __instance) {
17 | if(Plugin.Settings.ShowInCompletionScreen)
18 | _uiCreator.RemoveFloatingScreen();
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SliceDetails/AffinityPatches/MenuTransitionsHelperPatch.cs:
--------------------------------------------------------------------------------
1 | using SiraUtil.Affinity;
2 | using SliceDetails.UI;
3 |
4 | namespace SliceDetails.AffinityPatches
5 | {
6 | internal class MenuTransitionsHelperPatch : IAffinity
7 | {
8 | private readonly PauseUIController _pauseUIController;
9 |
10 | public MenuTransitionsHelperPatch(PauseUIController pauseUIController) {
11 | _pauseUIController = pauseUIController;
12 | }
13 |
14 | [AffinityPostfix]
15 | [AffinityPatch(typeof(MenuTransitionsHelper), nameof(MenuTransitionsHelper.HandleMainGameSceneDidFinish))]
16 | internal void Postfix(ref MenuTransitionsHelper __instance) {
17 | if (Plugin.Settings.ShowInPauseMenu)
18 | _pauseUIController.CleanUp();
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SliceDetails/Installers/SDGameInstaller.cs:
--------------------------------------------------------------------------------
1 | using SliceDetails.AffinityPatches;
2 | using SliceDetails.UI;
3 | using Zenject;
4 |
5 | namespace SliceDetails.Installers
6 | {
7 | internal class SDGameInstaller : Installer
8 | {
9 | public override void InstallBindings() {
10 | Container.BindInterfacesAndSelfTo().AsSingle();
11 | Container.Bind().FromNewComponentAsViewController().AsSingle();
12 | Container.Bind().AsSingle();
13 | Container.BindInterfacesAndSelfTo().AsSingle();
14 |
15 | Container.BindInterfacesTo().AsSingle();
16 | Container.BindInterfacesTo().AsSingle();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/SliceDetails/UI/HoverHintControllerGrabber.cs:
--------------------------------------------------------------------------------
1 | using HMUI;
2 | using Zenject;
3 |
4 | namespace SliceDetails.UI
5 | {
6 | internal class HoverHintControllerGrabber : IInitializable
7 | {
8 | private readonly HoverHintControllerHandler _hoverHintControllerHandler;
9 |
10 | private HoverHintController _hoverHintController;
11 |
12 | public HoverHintControllerGrabber(HoverHintControllerHandler hoverHintControllerHandler, HoverHintController hoverHintController) {
13 | _hoverHintControllerHandler = hoverHintControllerHandler;
14 | _hoverHintController = hoverHintController;
15 | }
16 |
17 | public void Initialize() {
18 | _hoverHintControllerHandler.SetOriginalHoverHintController(_hoverHintController);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SliceDetails/Installers/SDMenuInstaller.cs:
--------------------------------------------------------------------------------
1 | using SliceDetails.AffinityPatches;
2 | using SliceDetails.UI;
3 | using Zenject;
4 |
5 | namespace SliceDetails.Installers
6 | {
7 | internal class SDMenuInstaller : Installer
8 | {
9 | public override void InstallBindings() {
10 | Container.BindInterfacesAndSelfTo().AsSingle();
11 | Container.Bind().FromNewComponentAsViewController().AsSingle();
12 | Container.Bind().AsSingle();
13 |
14 | Container.BindInterfacesTo().AsSingle();
15 | Container.BindInterfacesTo().AsSingle();
16 | Container.BindInterfacesTo().AsSingle();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/SliceDetails/Utils/ColorSchemeManager.cs:
--------------------------------------------------------------------------------
1 | using IPA.Utilities;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using UnityEngine;
8 | using static PlayerSaveData;
9 |
10 | namespace SliceDetails.Utils
11 | {
12 | internal class ColorSchemeManager
13 | {
14 | private static ColorScheme _colorScheme;
15 |
16 | public static ColorScheme GetMainColorScheme() {
17 | if (_colorScheme == null) {
18 | ColorSchemesSettings colorSchemesSettings = ReflectionUtil.GetField(Resources.FindObjectsOfTypeAll().FirstOrDefault(), "_playerData").colorSchemesSettings;
19 | _colorScheme = colorSchemesSettings.GetSelectedColorScheme();
20 | }
21 | return _colorScheme;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SliceDetails
2 |
3 | A Beat Saber mod that lets you view your average cuts per-note angle per-grid position in the pause menu and level completion screen.
4 |
5 | 
6 |
7 | ## Installation
8 |
9 | - Install `BeatSaberMarkupLanguage` and `SiraUtil` from ModAssistant or manually
10 | - Download the [latest release](https://github.com/ckosmic/SliceDetails/releases/latest) and extract it into your Beat Saber directory
11 |
12 | ## Configuration
13 |
14 | Configuration is handled in UserData/SliceDetails.json:
15 | - `ShowHandle` (bool, default: false): Enables grabbable handles below the floating screens to allow you to position them
16 | - `TrueCutOffsets` (bool, default: true): If false, magnifies higher score offsets to help give a better look at which side of the block you need to improve on.
17 |
18 | ## License
19 | [MIT](https://choosealicense.com/licenses/mit/)
--------------------------------------------------------------------------------
/SliceDetails/Data/NoteInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using UnityEngine;
7 |
8 | namespace SliceDetails.Data
9 | {
10 | internal class NoteInfo
11 | {
12 | public NoteData noteData;
13 | public NoteCutInfo cutInfo;
14 | public float cutAngle;
15 | public float cutOffset;
16 | public Score score;
17 | public Vector2 noteGridPosition;
18 | public int noteIndex;
19 |
20 | public NoteInfo() {
21 |
22 | }
23 |
24 | public NoteInfo(NoteData noteData, NoteCutInfo cutInfo, float cutAngle, float cutOffset, Vector2 noteGridPosition, int noteIndex) {
25 | this.noteData = noteData;
26 | this.cutInfo = cutInfo;
27 | this.cutAngle = cutAngle;
28 | this.cutOffset = cutOffset;
29 | this.noteGridPosition = noteGridPosition;
30 | this.noteIndex = noteIndex;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SliceDetails/AffinityPatches/SoloFreePlayFlowCoordinatorPatch.cs:
--------------------------------------------------------------------------------
1 | using SiraUtil.Affinity;
2 | using SliceDetails.UI;
3 | using UnityEngine;
4 |
5 | namespace SliceDetails.AffinityPatches
6 | {
7 | internal class SoloFreePlayFlowCoordinatorPatch : IAffinity
8 | {
9 | private readonly UICreator _uiCreator;
10 |
11 | public SoloFreePlayFlowCoordinatorPatch(UICreator uiCreator) {
12 | _uiCreator = uiCreator;
13 | }
14 |
15 | [AffinityPostfix]
16 | [AffinityPatch(typeof(SoloFreePlayFlowCoordinator), "ProcessLevelCompletionResultsAfterLevelDidFinish")]
17 | internal void Postfix(ref SoloFreePlayFlowCoordinator __instance, LevelCompletionResults levelCompletionResults) {
18 | if (levelCompletionResults.levelEndAction == LevelCompletionResults.LevelEndAction.None && Plugin.Settings.ShowInCompletionScreen) {
19 | _uiCreator.CreateFloatingScreen(Plugin.Settings.ResultsUIPosition, Quaternion.Euler(Plugin.Settings.ResultsUIRotation));
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/SliceDetails/UI/HoverHintControllerHandler.cs:
--------------------------------------------------------------------------------
1 | using HMUI;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace SliceDetails.UI
9 | {
10 | internal class HoverHintControllerHandler
11 | {
12 | public HoverHintController hoverHintController {
13 | get {
14 | return (_hoverHintControllerCopy == null) ? _hoverHintControllerOriginal : _hoverHintControllerCopy;
15 | }
16 | }
17 |
18 | private HoverHintController _hoverHintControllerOriginal;
19 | private HoverHintController _hoverHintControllerCopy;
20 |
21 | internal void SetOriginalHoverHintController(HoverHintController original) {
22 | _hoverHintControllerOriginal = original;
23 | }
24 |
25 | internal void CloneHoverHintController() {
26 | _hoverHintControllerCopy = UnityEngine.Object.Instantiate(_hoverHintControllerOriginal);
27 | _hoverHintControllerCopy.transform.SetParent(null);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/SliceDetails/AffinityPatches/PartyFreePlayFlowCoordinatorPatch.cs:
--------------------------------------------------------------------------------
1 | using SiraUtil.Affinity;
2 | using SliceDetails.UI;
3 | using UnityEngine;
4 |
5 | namespace SliceDetails.AffinityPatches
6 | {
7 | internal class PartyFreePlayFlowCoordinatorPatch : IAffinity
8 | {
9 | private readonly UICreator _uiCreator;
10 |
11 | public PartyFreePlayFlowCoordinatorPatch(UICreator uiCreator)
12 | {
13 | _uiCreator = uiCreator;
14 | }
15 |
16 | [AffinityPostfix]
17 | [AffinityPatch(typeof(PartyFreePlayFlowCoordinator), "ProcessLevelCompletionResultsAfterLevelDidFinish")]
18 | internal void Postfix(ref PartyFreePlayFlowCoordinator __instance, LevelCompletionResults levelCompletionResults)
19 | {
20 | if (levelCompletionResults.levelEndAction == LevelCompletionResults.LevelEndAction.None && Plugin.Settings.ShowInCompletionScreen)
21 | {
22 | _uiCreator.CreateFloatingScreen(Plugin.Settings.ResultsUIPosition, Quaternion.Euler(Plugin.Settings.ResultsUIRotation));
23 | }
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/SliceDetails/Plugin.cs:
--------------------------------------------------------------------------------
1 | using IPA;
2 | using IPA.Config;
3 | using IPA.Config.Stores;
4 | using SiraUtil.Zenject;
5 | using SliceDetails.Installers;
6 | using SliceDetails.Settings;
7 | using BeatSaberMarkupLanguage.Settings;
8 | using SliceDetails.UI;
9 |
10 | namespace SliceDetails
11 | {
12 |
13 | [Plugin(RuntimeOptions.DynamicInit), NoEnableDisable]
14 | public class Plugin
15 | {
16 | internal static SettingsStore Settings { get; private set; }
17 |
18 | [Init]
19 | public void Init(IPA.Logging.Logger logger, Config config, Zenjector zenject) {
20 | Settings = config.Generated();
21 |
22 | BSMLSettings.instance.AddSettingsMenu("SliceDetails", $"SliceDetails.UI.Views.settingsView.bsml", SettingsViewController.instance);
23 |
24 | zenject.UseLogger(logger);
25 |
26 | zenject.Install(Location.App);
27 | zenject.Install(Location.Menu);
28 | zenject.Install(Location.StandardPlayer);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/SliceDetails/Settings/SettingsStore.cs:
--------------------------------------------------------------------------------
1 | using IPA.Config.Stores;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Runtime.CompilerServices;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using UnityEngine;
9 |
10 | [assembly: InternalsVisibleTo(GeneratedStore.AssemblyVisibilityTarget)]
11 | namespace SliceDetails.Settings
12 | {
13 | internal class SettingsStore
14 | {
15 | public Vector3 ResultsUIPosition = new Vector3(-3.25f, 3.25f, 1.75f);
16 | public Vector3 ResultsUIRotation = new Vector3(340.0f, 292.0f, 0.0f);
17 | public Vector3 PauseUIPosition = new Vector3(-3.0f, 1.5f, 0.0f);
18 | public Vector3 PauseUIRotation = new Vector3(0.0f, 270.0f, 0.0f);
19 | public bool ShowInPauseMenu = true;
20 | public bool ShowInCompletionScreen = true;
21 | public bool ShowHandle = false;
22 | public bool TrueCutOffsets = true;
23 | public bool CountArcs = true;
24 | public bool CountChains = true;
25 | public bool ShowSliceCounts = true;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 ckosmic
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/SliceDetails/AffinityPatches/PauseMenuManagerPatches.cs:
--------------------------------------------------------------------------------
1 | using SiraUtil.Affinity;
2 | using SliceDetails.UI;
3 |
4 | namespace SliceDetails.AffinityPatches
5 | {
6 | internal class PauseMenuManagerPatches : IAffinity
7 | {
8 | private readonly PauseUIController _pauseUIController;
9 |
10 | public PauseMenuManagerPatches(PauseUIController pauseUIController) {
11 | _pauseUIController = pauseUIController;
12 | }
13 |
14 | [AffinityPostfix]
15 | [AffinityPatch(typeof(PauseMenuManager), nameof(PauseMenuManager.ShowMenu))]
16 | internal void ShowMenuPostfix(ref PauseMenuManager __instance) {
17 | if (Plugin.Settings.ShowInPauseMenu && _pauseUIController != null)
18 | _pauseUIController.PauseMenuOpened(__instance);
19 | }
20 |
21 | [AffinityPostfix]
22 | [AffinityPatch(typeof(PauseMenuManager), nameof(PauseMenuManager.StartResumeAnimation))]
23 | internal void StartResumeAnimationPostfix(ref PauseMenuManager __instance) {
24 | if (Plugin.Settings.ShowInPauseMenu && _pauseUIController != null)
25 | _pauseUIController.PauseMenuClosed(__instance);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/SliceDetails/UI/AssetLoader.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Reflection;
3 | using UnityEngine;
4 |
5 | namespace SliceDetails.UI
6 | {
7 | internal class AssetLoader
8 | {
9 | public Sprite spr_arrow { get; }
10 | public Sprite spr_dot { get; }
11 | public Sprite spr_roundrect { get; }
12 |
13 | public AssetLoader() {
14 | spr_arrow = LoadSpriteFromResource("SliceDetails.Resources.arrow.png");
15 | spr_dot = LoadSpriteFromResource("SliceDetails.Resources.dot.png");
16 | spr_roundrect = LoadSpriteFromResource("SliceDetails.Resources.bloq.png");
17 | }
18 |
19 | public static Sprite LoadSpriteFromResource(string path) {
20 | Assembly assembly = Assembly.GetCallingAssembly();
21 | using (Stream stream = assembly.GetManifestResourceStream(path)) {
22 | if (stream != null) {
23 | byte[] data = new byte[stream.Length];
24 | stream.Read(data, 0, (int)stream.Length);
25 | Texture2D tex = new Texture2D(2, 2);
26 | if (tex.LoadImage(data)) {
27 | Sprite sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0, 0), 100);
28 | return sprite;
29 | }
30 | }
31 | }
32 | return null;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/SliceDetails.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30413.136
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SliceDetails", "SliceDetails\SliceDetails.csproj", "{15CDF006-CF49-4774-8C0E-8B5DA75D0194}"
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 | {15CDF006-CF49-4774-8C0E-8B5DA75D0194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {15CDF006-CF49-4774-8C0E-8B5DA75D0194}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {15CDF006-CF49-4774-8C0E-8B5DA75D0194}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {15CDF006-CF49-4774-8C0E-8B5DA75D0194}.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 = {E1EABC81-D6F4-4474-9977-439F9381E319}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/SliceDetails/UI/Views/settingsView.bsml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SliceDetails/Data/Score.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace SliceDetails.Data
8 | {
9 | internal class Score
10 | {
11 | public float PreSwing { get; set; }
12 | public float PostSwing { get; set; }
13 | public float Offset { get; set; }
14 | public float TotalScore {
15 | get
16 | {
17 | if (PreSwing < 0)
18 | return PostSwing + Offset;
19 | if (PostSwing < 0)
20 | return PreSwing + Offset;
21 | return PreSwing + PostSwing + Offset;
22 | }
23 | }
24 |
25 | public bool CountPreSwing { get; set; } = true;
26 | public bool CountPostSwing { get; set; } = true;
27 |
28 | public Score(float? preSwing, float? postSwing, float offset) {
29 | if (preSwing != null)
30 | PreSwing = (float)preSwing;
31 | else
32 | CountPreSwing = false;
33 | if (postSwing != null)
34 | PostSwing = (float)postSwing;
35 | else
36 | CountPostSwing = false;
37 | Offset = offset;
38 | }
39 |
40 | public static Score operator +(Score a, Score b) => new Score(a.PreSwing + b.PreSwing, a.PostSwing + b.PostSwing, a.Offset + b.Offset);
41 | public static Score operator /(Score a, float b) => new Score(a.PreSwing / b, a.PostSwing / b, a.Offset / b);
42 | public static Score operator /(Score a, Score b) => new Score(a.PreSwing / b.PreSwing, a.PostSwing / b.PostSwing, a.Offset / b.Offset);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/SliceDetails/UI/SettingsViewController.cs:
--------------------------------------------------------------------------------
1 | using BeatSaberMarkupLanguage.Attributes;
2 |
3 | namespace SliceDetails.UI
4 | {
5 | internal class SettingsViewController : PersistentSingleton
6 | {
7 | [UIValue("show-pause")]
8 | public bool ShowInPauseMenu {
9 | get { return Plugin.Settings.ShowInPauseMenu; }
10 | set { Plugin.Settings.ShowInPauseMenu = value; }
11 | }
12 |
13 | [UIValue("show-completion")]
14 | public bool ShowInCompletionScreen
15 | {
16 | get { return Plugin.Settings.ShowInCompletionScreen; }
17 | set { Plugin.Settings.ShowInCompletionScreen = value; }
18 | }
19 |
20 | [UIValue("show-handles")]
21 | public bool ShowHandle
22 | {
23 | get { return Plugin.Settings.ShowHandle; }
24 | set { Plugin.Settings.ShowHandle = value; }
25 | }
26 |
27 | [UIValue("show-counts")]
28 | public bool ShowSliceCounts
29 | {
30 | get { return Plugin.Settings.ShowSliceCounts; }
31 | set { Plugin.Settings.ShowSliceCounts = value; }
32 | }
33 |
34 | [UIValue("true-offsets")]
35 | public bool TrueCutOffsets
36 | {
37 | get { return Plugin.Settings.TrueCutOffsets; }
38 | set { Plugin.Settings.TrueCutOffsets = value; }
39 | }
40 |
41 | [UIValue("count-arcs")]
42 | public bool CountArcs
43 | {
44 | get { return Plugin.Settings.CountArcs; }
45 | set { Plugin.Settings.CountArcs = value; }
46 | }
47 |
48 | [UIValue("count-chains")]
49 | public bool CountChains
50 | {
51 | get { return Plugin.Settings.CountChains; }
52 | set { Plugin.Settings.CountChains = value; }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/SliceDetails/SliceProcessor.cs:
--------------------------------------------------------------------------------
1 | using SliceDetails.UI;
2 | using SliceDetails.Data;
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace SliceDetails
7 | {
8 | internal class SliceProcessor
9 | {
10 | public Tile[] tiles = new Tile[12];
11 | public bool ready { get; private set; } = false;
12 |
13 | public void ResetProcessor() {
14 | ready = false;
15 |
16 | tiles = new Tile[12];
17 |
18 | // Create "tiles", basically allocate information about each position in the 4x3 note grid.
19 | for (int i = 0; i < 12; i++) {
20 | tiles[i] = new Tile();
21 | for (int j = 0; j < 18; j++) {
22 | tiles[i].tileNoteInfos[j] = new List();
23 | }
24 | }
25 | }
26 |
27 | public void ProcessSlices(List noteInfos) {
28 | ResetProcessor();
29 |
30 | // Populate the tiles' note infos. Each List in tileNoteInfos cooresponds to each direction/color combination (i.e. DownLeft/ColorA)
31 | // where elements 0-8 are ColorA notes and elements 9-17 are ColorB notes numbering from NoteCutDirection.Up (0) to NoteCutDirection.Any (8)
32 | foreach (NoteInfo ni in noteInfos) {
33 | int noteDirection = (int)Enum.Parse(typeof(OrderedNoteCutDirection), ni.noteData.cutDirection.ToString());
34 | int noteColor = (int)ni.noteData.colorType;
35 | int tileNoteDataIndex = noteColor * 9 + noteDirection;
36 |
37 | tiles[ni.noteIndex].tileNoteInfos[tileNoteDataIndex].Add(ni);
38 | }
39 |
40 | // Calculate average angles and offsets
41 | for (int i = 0; i < 12; i++) {
42 | tiles[i].CalculateAverages();
43 | }
44 |
45 | ready = true;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/SliceDetails/UI/PauseUIController.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using Zenject;
3 |
4 | namespace SliceDetails.UI
5 | {
6 | internal class PauseUIController : IInitializable
7 | {
8 | private readonly SliceRecorder _sliceRecorder;
9 | private readonly UICreator _uiCreator;
10 | private readonly HoverHintControllerHandler _hoverHintControllerHandler;
11 |
12 | private GridViewController _gridViewController;
13 | private GameObject _gridViewControllerParent;
14 |
15 | public PauseUIController(SliceRecorder sliceRecorder, UICreator uiCreator, GridViewController gridViewController, HoverHintControllerHandler hoverHintControllerHandler) {
16 | _sliceRecorder = sliceRecorder;
17 | _uiCreator = uiCreator;
18 | _gridViewController = gridViewController;
19 | _hoverHintControllerHandler = hoverHintControllerHandler;
20 | }
21 |
22 | public void Initialize() {
23 | if (Plugin.Settings.ShowInPauseMenu) {
24 | _hoverHintControllerHandler.CloneHoverHintController();
25 | _uiCreator.CreateFloatingScreen(Plugin.Settings.PauseUIPosition, Quaternion.Euler(Plugin.Settings.PauseUIRotation));
26 | _gridViewControllerParent = _gridViewController.transform.parent.gameObject;
27 | _gridViewControllerParent?.SetActive(false);
28 | }
29 | }
30 |
31 | public void PauseMenuOpened(PauseMenuManager pauseMenuManager) {
32 | _gridViewControllerParent?.SetActive(true);
33 | _uiCreator.ParentFloatingScreen(pauseMenuManager.transform);
34 | _sliceRecorder.ProcessSlices();
35 | _gridViewController.SetTileScores();
36 | }
37 |
38 | public void PauseMenuClosed(PauseMenuManager pauseMenuManager) {
39 | _gridViewController.CloseModal(false);
40 | _gridViewControllerParent?.SetActive(false);
41 | }
42 |
43 | public void CleanUp() {
44 | _gridViewController.CloseModal(false);
45 | _uiCreator.RemoveFloatingScreen();
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/SliceDetails/Data/Tile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using UnityEngine;
7 |
8 | namespace SliceDetails.Data
9 | {
10 | internal class Tile
11 | {
12 | public List[] tileNoteInfos = new List[18];
13 | public float[] angleAverages = new float[18];
14 | public float[] offsetAverages = new float[18];
15 | public Score[] scoreAverages = new Score[18];
16 | public int[] noteCounts = new int[18];
17 | public float scoreAverage = 0.0f;
18 | public bool atLeastOneNote = false;
19 | public int noteCount = 0;
20 |
21 | public void CalculateAverages() {
22 | angleAverages = new float[18];
23 | offsetAverages = new float[18];
24 | scoreAverages = new Score[18];
25 | noteCounts = new int[18];
26 | scoreAverage = 0.0f;
27 | atLeastOneNote = false;
28 |
29 | for (int i = 0; i < 18; i++) {
30 | scoreAverages[i] = new Score(0.0f, 0.0f, 0.0f);
31 | }
32 |
33 | noteCount = 0;
34 | for (int i = 0; i < tileNoteInfos.Length; i++) {
35 | if (tileNoteInfos[i].Count > 0) {
36 | int preSwingCount = 0;
37 | int postSwingCount = 0;
38 | Vector2 angleXYAverages = Vector2.zero;
39 | foreach (NoteInfo noteInfo in tileNoteInfos[i]) {
40 | atLeastOneNote = true;
41 | angleXYAverages.x += Mathf.Cos(noteInfo.cutAngle * Mathf.PI / 180f);
42 | angleXYAverages.y += Mathf.Sin(noteInfo.cutAngle * Mathf.PI / 180f);
43 | offsetAverages[i] += noteInfo.cutOffset;
44 | scoreAverages[i] += noteInfo.score;
45 | noteCounts[i]++;
46 | scoreAverage += noteInfo.score.TotalScore;
47 | preSwingCount += scoreAverages[i].CountPreSwing ? 1 : 0;
48 | postSwingCount += scoreAverages[i].CountPostSwing ? 1 : 0;
49 | noteCount++;
50 | }
51 | angleXYAverages.x /= tileNoteInfos[i].Count;
52 | angleXYAverages.y /= tileNoteInfos[i].Count;
53 | angleAverages[i] = Mathf.Atan2(angleXYAverages.y, angleXYAverages.x) * 180f / Mathf.PI;
54 | offsetAverages[i] /= tileNoteInfos[i].Count;
55 | scoreAverages[i].PreSwing /= preSwingCount;
56 | scoreAverages[i].PostSwing /= postSwingCount;
57 | scoreAverages[i].Offset /= tileNoteInfos[i].Count;
58 | }
59 | }
60 | scoreAverage /= noteCount;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/SliceDetails/UI/SelectedTileIndicator.cs:
--------------------------------------------------------------------------------
1 | using HMUI;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using UnityEngine.UI;
5 | using BeatSaberMarkupLanguage;
6 |
7 | namespace SliceDetails.UI
8 | {
9 | internal class SelectedTileIndicator : MonoBehaviour
10 | {
11 | private static Sprite newRoundRect;
12 | private List _tileDots = new List();
13 |
14 | public void Initialize(AssetLoader assetLoader) {
15 | // Create a new sliced sprite using the existing bloq texture so we don't have to make another resource
16 | if (newRoundRect == null) {
17 | Vector4 spriteBorder = new Vector4(54, 54, 54, 54);
18 | Texture2D spriteTexture = assetLoader.spr_roundrect.texture;
19 | Rect rect = new Rect(0, 0, spriteTexture.width, spriteTexture.height);
20 | newRoundRect = Sprite.Create(spriteTexture, rect, new Vector2(0.5f, 0.5f), 200, 1, SpriteMeshType.FullRect, spriteBorder);
21 | }
22 |
23 | ImageView background = new GameObject("Background").AddComponent();
24 | background.transform.SetParent(transform, false);
25 | background.rectTransform.localScale = Vector3.one;
26 | background.rectTransform.localPosition = Vector3.zero;
27 | background.rectTransform.sizeDelta = new Vector2(15.0f, 12.0f);
28 | background.sprite = newRoundRect;
29 | background.type = Image.Type.Sliced;
30 | background.color = new Color(0.125f, 0.125f, 0.125f, 0.75f);
31 | background.material = Utilities.ImageResources.NoGlowMat;
32 |
33 | for (int i = 0; i < 12; i++) {
34 | ImageView tileDot = new GameObject("TileDot").AddComponent();
35 | tileDot.transform.SetParent(background.transform, false);
36 | tileDot.rectTransform.localScale = Vector3.one;
37 | tileDot.rectTransform.localPosition = new Vector3((i % 4) * 3.0f - 4.5f, (i / 4) * 3.0f - 3.0f, 0.0f);
38 | tileDot.rectTransform.sizeDelta = new Vector2(6.0f, 6.0f);
39 | tileDot.sprite = assetLoader.spr_dot;
40 | tileDot.type = Image.Type.Simple;
41 | tileDot.color = Color.white;
42 | tileDot.material = Utilities.ImageResources.NoGlowMat;
43 | _tileDots.Add(tileDot);
44 | }
45 | }
46 |
47 | public void SetSelectedTile(int tileIndex) {
48 | for (int i = 0; i < _tileDots.Count; i++) {
49 | _tileDots[i].color = (tileIndex == i ? Color.white : new Color(0.5f, 0.5f, 0.5f, 0.7f));
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/SliceDetails/UI/Views/gridView.bsml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/SliceDetails/UI/UICreator.cs:
--------------------------------------------------------------------------------
1 | using BeatSaberMarkupLanguage.FloatingScreen;
2 | using HMUI;
3 | using SiraUtil.Logging;
4 | using UnityEngine;
5 | using UnityEngine.SceneManagement;
6 |
7 | namespace SliceDetails.UI
8 | {
9 | internal class UICreator
10 | {
11 | private readonly GridViewController _gridViewController;
12 | private readonly SliceProcessor _sliceProcessor;
13 | private readonly SiraLog _siraLog;
14 |
15 | private FloatingScreen _floatingScreen;
16 |
17 | public HoverHintController hoverHintController;
18 |
19 | public UICreator(SiraLog siraLog, GridViewController gridViewController, SliceProcessor sliceProcesssor) {
20 | _siraLog = siraLog;
21 | _gridViewController = gridViewController;
22 | _sliceProcessor = sliceProcesssor;
23 | }
24 |
25 | public void CreateFloatingScreen(Vector3 position, Quaternion rotation) {
26 | _siraLog.Info("Creating floating screen: " + (_gridViewController == null));
27 | _gridViewController.UpdateUINotesHoverHintController();
28 |
29 |
30 | _floatingScreen = FloatingScreen.CreateFloatingScreen(new Vector2(150f, 120f), true, position, rotation);
31 | _floatingScreen.SetRootViewController(_gridViewController, ViewController.AnimationType.None);
32 | _floatingScreen.ShowHandle = Plugin.Settings.ShowHandle;
33 | _floatingScreen.HandleSide = FloatingScreen.Side.Bottom;
34 | _floatingScreen.HighlightHandle = true;
35 | _floatingScreen.handle.transform.localScale = Vector3.one * 5.0f;
36 | _floatingScreen.handle.transform.localPosition = new Vector3(0.0f, -25.0f, 0.0f);
37 | _floatingScreen.HandleReleased += OnHandleReleased;
38 | _floatingScreen.gameObject.name = "SliceDetailsScreen";
39 | _floatingScreen.transform.localScale = Vector3.one * 0.03f;
40 |
41 | _gridViewController.SetTileScores();
42 | _gridViewController.transform.localScale = Vector3.one;
43 | _gridViewController.transform.localEulerAngles = Vector3.zero;
44 | _gridViewController.gameObject.SetActive(true);
45 | }
46 |
47 | public void RemoveFloatingScreen() {
48 | // Destroying the hover hint panel breaks everything so move it out of the screen before destroying
49 | if (_floatingScreen != null) {
50 | if (_floatingScreen.transform.GetComponentInChildren(true)) {
51 | Transform hoverHintPanel = _floatingScreen.transform.GetComponentInChildren(true).transform;
52 | hoverHintPanel.SetParent(null);
53 | }
54 | _gridViewController.transform.SetParent(null);
55 | _gridViewController.gameObject.SetActive(false);
56 | UnityEngine.Object.Destroy(_floatingScreen.gameObject);
57 | }
58 | _sliceProcessor.ResetProcessor();
59 | }
60 |
61 | public void ParentFloatingScreen(Transform parent) {
62 | _floatingScreen.transform.SetParent(parent);
63 | }
64 |
65 | private void OnHandleReleased(object sender, FloatingScreenHandleEventArgs args) {
66 | if (SceneManager.GetActiveScene().name == "MainMenu") {
67 | Plugin.Settings.ResultsUIPosition = _floatingScreen.transform.position;
68 | Plugin.Settings.ResultsUIRotation = _floatingScreen.transform.eulerAngles;
69 | } else if (SceneManager.GetActiveScene().name == "GameCore") {
70 | Plugin.Settings.PauseUIPosition = _floatingScreen.transform.position;
71 | Plugin.Settings.PauseUIRotation = _floatingScreen.transform.eulerAngles;
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/SliceDetails/SliceRecorder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using Zenject;
5 | using SliceDetails.Data;
6 | using SiraUtil.Logging;
7 |
8 | namespace SliceDetails
9 | {
10 | internal class SliceRecorder : UnityEngine.Object, IInitializable, IDisposable
11 | {
12 | private readonly BeatmapObjectManager _beatmapObjectManager;
13 | private readonly SliceProcessor _sliceProcessor;
14 | private readonly ScoreController _scoreController;
15 |
16 | [Inject] private SiraLog _siraLog;
17 |
18 | private Dictionary _noteSwingInfos = new Dictionary();
19 | private List _noteInfos = new List();
20 |
21 | public SliceRecorder(BeatmapObjectManager beatmapObjectManager, ScoreController scoreController, SliceProcessor sliceProcessor) {
22 | _beatmapObjectManager = beatmapObjectManager;
23 | _scoreController = scoreController;
24 | _sliceProcessor = sliceProcessor;
25 | }
26 |
27 | public void Initialize() {
28 | _beatmapObjectManager.noteWasCutEvent += OnNoteWasCut;
29 | _scoreController.scoringForNoteFinishedEvent += ScoringForNoteFinishedHandler;
30 | _sliceProcessor.ResetProcessor();
31 | }
32 |
33 | public void Dispose() {
34 | _beatmapObjectManager.noteWasCutEvent -= OnNoteWasCut;
35 | _scoreController.scoringForNoteFinishedEvent -= ScoringForNoteFinishedHandler;
36 | // Process slices once the map ends
37 | ProcessSlices();
38 | }
39 |
40 | public void ClearSlices() {
41 | _noteInfos.Clear();
42 | ProcessSlices();
43 | }
44 |
45 | public void ProcessSlices() {
46 | _sliceProcessor.ProcessSlices(_noteInfos);
47 | }
48 |
49 | private void OnNoteWasCut(NoteController noteController, in NoteCutInfo noteCutInfo) {
50 | if (noteController.noteData.colorType == ColorType.None || !noteCutInfo.allIsOK) return;
51 | ProcessNote(noteController, noteCutInfo);
52 | }
53 |
54 | private void ProcessNote(NoteController noteController, NoteCutInfo noteCutInfo) {
55 | if (noteController == null) return;
56 |
57 | Vector2 noteGridPosition;
58 | noteGridPosition.y = (int)noteController.noteData.noteLineLayer;
59 | noteGridPosition.x = noteController.noteData.lineIndex;
60 | int noteIndex = (int)(noteGridPosition.y * 4 + noteGridPosition.x);
61 |
62 | // No ME notes allowed >:(
63 | if (noteGridPosition.x >= 4 || noteGridPosition.y >= 3 || noteGridPosition.x < 0 || noteGridPosition.y < 0) return;
64 |
65 | Vector2 cutDirection = new Vector3(-noteCutInfo.cutNormal.y, noteCutInfo.cutNormal.x);
66 | float cutAngle = Mathf.Atan2(cutDirection.y, cutDirection.x) * Mathf.Rad2Deg + 180f;
67 |
68 | float cutOffset = noteCutInfo.cutDistanceToCenter;
69 | Vector3 noteCenter = noteController.noteTransform.position;
70 | if (Vector3.Dot(noteCutInfo.cutNormal, noteCutInfo.cutPoint - noteCenter) > 0f)
71 | {
72 | cutOffset = -cutOffset;
73 | }
74 |
75 | NoteInfo noteInfo = new NoteInfo(noteController.noteData, noteCutInfo, cutAngle, cutOffset, noteGridPosition, noteIndex);
76 |
77 | if (!_noteSwingInfos.ContainsKey(noteController.noteData))
78 | {
79 | _noteSwingInfos.Add(noteController.noteData, noteInfo);
80 | }
81 | }
82 |
83 | public void ScoringForNoteFinishedHandler(ScoringElement scoringElement) {
84 | NoteInfo noteSwingInfo;
85 | if (_noteSwingInfos.TryGetValue(scoringElement.noteData, out noteSwingInfo))
86 | {
87 | GoodCutScoringElement goodScoringElement = (GoodCutScoringElement)scoringElement;
88 |
89 | IReadonlyCutScoreBuffer cutScoreBuffer = goodScoringElement.cutScoreBuffer;
90 |
91 | int preSwing = cutScoreBuffer.beforeCutScore;
92 | int postSwing = cutScoreBuffer.afterCutScore;
93 | int offset = cutScoreBuffer.centerDistanceCutScore;
94 |
95 | switch (goodScoringElement.noteData.scoringType)
96 | {
97 | case NoteData.ScoringType.Normal:
98 | noteSwingInfo.score = new Score(preSwing, postSwing, offset);
99 | _noteInfos.Add(noteSwingInfo);
100 | break;
101 | case NoteData.ScoringType.SliderHead:
102 | if (!Plugin.Settings.CountArcs) break;
103 | noteSwingInfo.score = new Score(preSwing, null, offset);
104 | _noteInfos.Add(noteSwingInfo);
105 | break;
106 | case NoteData.ScoringType.SliderTail:
107 | if (!Plugin.Settings.CountArcs) break;
108 | noteSwingInfo.score = new Score(null, postSwing, offset);
109 | _noteInfos.Add(noteSwingInfo);
110 | break;
111 | case NoteData.ScoringType.BurstSliderHead:
112 | if (!Plugin.Settings.CountChains) break;
113 | noteSwingInfo.score = new Score(preSwing, null, offset);
114 | _noteInfos.Add(noteSwingInfo);
115 | break;
116 | }
117 |
118 | _noteSwingInfos.Remove(goodScoringElement.noteData);
119 | }
120 | else {
121 | // Bad cut, do nothing
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/SliceDetails/SliceDetails.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net472
5 | Library
6 | 9
7 | disable
8 | false
9 | ..\Refs
10 | $(LocalRefsDir)
11 | $(MSBuildProjectDirectory)\
12 | portable
13 |
14 |
15 |
16 |
17 | $(BeatSaberDir)\Beat Saber_Data\Managed\BeatmapCore.dll
18 |
19 |
20 | $(BeatSaberDir)\Plugins\BSML.dll
21 |
22 |
23 | $(BeatSaberDir)\Beat Saber_Data\Managed\Colors.dll
24 |
25 |
26 | $(BeatSaberDir)\Beat Saber_Data\Managed\GameplayCore.dll
27 |
28 |
29 | $(BeatSaberDir)\Plugins\SiraUtil.dll
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | $(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll
39 | False
40 |
41 |
42 | $(BeatSaberDir)\Beat Saber_Data\Managed\HMLib.dll
43 | False
44 |
45 |
46 | $(BeatSaberDir)\Beat Saber_Data\Managed\HMUI.dll
47 | False
48 |
49 |
50 | $(BeatSaberDir)\Beat Saber_Data\Managed\IPA.Loader.dll
51 | False
52 |
53 |
54 | $(BeatSaberDir)\Beat Saber_Data\Managed\Unity.TextMeshPro.dll
55 | False
56 |
57 |
58 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.dll
59 | False
60 |
61 |
62 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.AnimationModule.dll
63 |
64 |
65 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.AudioModule.dll
66 |
67 |
68 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll
69 | False
70 |
71 |
72 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.ImageConversionModule.dll
73 |
74 |
75 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.InputLegacyModule.dll
76 |
77 |
78 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.PhysicsModule.dll
79 |
80 |
81 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UI.dll
82 | False
83 |
84 |
85 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UIElementsModule.dll
86 | False
87 |
88 |
89 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UIModule.dll
90 | False
91 |
92 |
93 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.VRModule.dll
94 | False
95 |
96 |
97 | $(BeatSaberDir)\Beat Saber_Data\Managed\VRUI.dll
98 |
99 |
100 | $(BeatSaberDir)\Beat Saber_Data\Managed\Zenject.dll
101 |
102 |
103 | $(BeatSaberDir)\Beat Saber_Data\Managed\Zenject-usage.dll
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | build; native; contentfiles; analyzers; buildtransitive
121 | all
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/SliceDetails/UI/NoteUI.cs:
--------------------------------------------------------------------------------
1 | using HMUI;
2 | using IPA.Utilities;
3 | using SliceDetails.Utils;
4 | using SliceDetails.Data;
5 | using System;
6 | using UnityEngine;
7 | using TMPro;
8 | using UnityEngine.UI;
9 |
10 | namespace SliceDetails.UI
11 | {
12 |
13 | internal class NoteUI : MonoBehaviour
14 | {
15 | private ImageView _directionArrowImage;
16 | private Transform _cutGroup;
17 | private ImageView _cutArrowImage;
18 | private ImageView _cutDistanceImage;
19 | private ImageView _backgroundImage;
20 |
21 | private Color _noteColor;
22 | private HoverHint _noteHoverHint;
23 | private HoverHintController _hoverHintController;
24 | private TextMeshProUGUI _hoverPanelTmpro;
25 |
26 | private float _noteRotation;
27 |
28 | public void Initialize(OrderedNoteCutDirection cutDirection, ColorType colorType, AssetLoader assetLoader) {
29 | transform.localScale = Vector3.one * 0.9f;
30 |
31 | _backgroundImage = GetComponent();
32 | _directionArrowImage = transform.Find("NoteDirArrow").GetComponent();
33 | _cutArrowImage = transform.Find("NoteCutArrow").GetComponent();
34 | _cutDistanceImage = transform.Find("NoteCutDistance").GetComponent();
35 |
36 | _cutGroup = new GameObject("NoteCutGroup").transform;
37 | _cutGroup.SetParent(_backgroundImage.transform);
38 | _cutGroup.localPosition = Vector3.zero;
39 | _cutGroup.localScale = Vector3.one;
40 | _cutGroup.localRotation = Quaternion.identity;
41 | _cutArrowImage.transform.SetParent(_cutGroup);
42 | _cutDistanceImage.transform.SetParent(_cutGroup);
43 |
44 | if (colorType == ColorType.ColorA)
45 | _noteColor = ColorSchemeManager.GetMainColorScheme().saberAColor;
46 | else if (colorType == ColorType.ColorB)
47 | _noteColor = ColorSchemeManager.GetMainColorScheme().saberBColor;
48 |
49 | _backgroundImage.color = _noteColor;
50 | _cutDistanceImage.color = new Color(0.0f, 1.0f, 0.0f, 0.75f);
51 |
52 | Texture2D square = new Texture2D(2, 2);
53 | square.filterMode = FilterMode.Point;
54 | square.Apply();
55 | _cutDistanceImage.sprite = Sprite.Create(square, new Rect(0, 0, square.width, square.height), new Vector2(0, 0), 100);
56 |
57 | _noteHoverHint = _backgroundImage.gameObject.AddComponent();
58 | _noteHoverHint.text = "";
59 |
60 |
61 | switch (cutDirection) {
62 | case OrderedNoteCutDirection.Down:
63 | _noteRotation = 0.0f;
64 | break;
65 | case OrderedNoteCutDirection.Up:
66 | _noteRotation = 180.0f;
67 | break;
68 | case OrderedNoteCutDirection.Left:
69 | _noteRotation = 270.0f;
70 | break;
71 | case OrderedNoteCutDirection.Right:
72 | _noteRotation = 90.0f;
73 | break;
74 | case OrderedNoteCutDirection.DownLeft:
75 | _noteRotation = 315.0f;
76 | break;
77 | case OrderedNoteCutDirection.DownRight:
78 | _noteRotation = 45.0f;
79 | break;
80 | case OrderedNoteCutDirection.UpLeft:
81 | _noteRotation = 225.0f;
82 | break;
83 | case OrderedNoteCutDirection.UpRight:
84 | _noteRotation = 135.0f;
85 | break;
86 | case OrderedNoteCutDirection.Any:
87 | _noteRotation = 0.0f;
88 | _directionArrowImage.sprite = assetLoader.spr_dot;
89 | break;
90 | }
91 |
92 | transform.localRotation = Quaternion.Euler(new Vector3(0f, 0f, _noteRotation));
93 | }
94 |
95 | public void SetHoverHintController(HoverHintController hoverHintController) {
96 | _hoverHintController = hoverHintController;
97 | HoverHintPanel hhp = _hoverHintController.GetField("_hoverHintPanel");
98 | // Skew cringe skew cringe
99 | hhp.GetComponent().SetField("_skew", 0.0f);
100 | _hoverPanelTmpro = hhp.GetComponentInChildren();
101 | _hoverPanelTmpro.fontStyle = FontStyles.Normal;
102 | _hoverPanelTmpro.alignment = TextAlignmentOptions.Left;
103 | _hoverPanelTmpro.overflowMode = TextOverflowModes.Overflow;
104 | _hoverPanelTmpro.enableWordWrapping = false;
105 | ContentSizeFitter csf = _hoverPanelTmpro.gameObject.AddComponent();
106 | csf.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
107 | }
108 |
109 | public void SetNoteData(float angle, float offset, Score score, int count) {
110 | _noteHoverHint.SetField("_hoverHintController", _hoverHintController);
111 |
112 | if (angle == 0f && offset == 0f) {
113 | _backgroundImage.color = Color.gray;
114 | _cutArrowImage.gameObject.SetActive(false);
115 | _cutDistanceImage.gameObject.SetActive(false);
116 | _directionArrowImage.color = new Color(0.8f, 0.8f, 0.8f);
117 | _noteHoverHint.text = "";
118 | } else {
119 | _backgroundImage.color = _noteColor;
120 | _cutArrowImage.gameObject.SetActive(true);
121 | _cutDistanceImage.gameObject.SetActive(true);
122 | _cutGroup.transform.localRotation = Quaternion.Euler(new Vector3(0f, 0f, angle - _noteRotation - 90f));
123 | if (Plugin.Settings.TrueCutOffsets) {
124 | _cutArrowImage.transform.localPosition = new Vector3(offset * 20.0f, 0f, 0f);
125 | _cutDistanceImage.transform.localScale = new Vector2(-offset * 1.33f, 1.0f);
126 | } else {
127 | _cutArrowImage.transform.localPosition = new Vector3(offset * (30.0f + score.Offset), 0f, 0f);
128 | _cutDistanceImage.transform.localScale = new Vector2(-offset * (1.995f + score.Offset*0.0665f), 1.0f);
129 | }
130 | _directionArrowImage.color = Color.white;
131 | string noteNotes = count == 1 ? "note" : "notes";
132 | _noteHoverHint.text = "Average score (" + count + " " + noteNotes + ")\n" + String.Format("{0:0.00}", score.TotalScore) + "\nPre-swing - " + String.Format("{0:0.00}", score.PreSwing) + "\nPost-swing - " + String.Format("{0:0.00}", score.PostSwing) + "\nAccuracy - " + String.Format("{0:0.00}", score.Offset) + "";
133 | }
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/SliceDetails/UI/GridViewController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using BeatSaberMarkupLanguage.Attributes;
5 | using BeatSaberMarkupLanguage.Components;
6 | using BeatSaberMarkupLanguage.ViewControllers;
7 | using HMUI;
8 | using IPA.Utilities;
9 | using SiraUtil.Logging;
10 | using TMPro;
11 | using UnityEngine;
12 | using UnityEngine.EventSystems;
13 | using Zenject;
14 | using SliceDetails.Data;
15 | using UnityEngine.SceneManagement;
16 |
17 | namespace SliceDetails.UI
18 | {
19 | public enum OrderedNoteCutDirection
20 | {
21 | UpLeft = 0,
22 | Up = 1,
23 | UpRight = 2,
24 | Left = 3,
25 | Any = 4,
26 | Right = 5,
27 | DownLeft = 6,
28 | Down = 7,
29 | DownRight = 8,
30 | None = 9
31 | }
32 |
33 | [HotReload(RelativePathToLayout = @"Views\gridView.bsml")]
34 | [ViewDefinition("SliceDetails.UI.Views.gridView.bsml")]
35 | internal class GridViewController : BSMLAutomaticViewController {
36 |
37 | private SiraLog _siraLog;
38 | private AssetLoader _assetLoader;
39 | private HoverHintControllerHandler _hoverHintControllerHandler;
40 | private SliceProcessor _sliceProcessor;
41 | private DiContainer _diContainer;
42 |
43 | [UIObject("tile-grid")]
44 | private readonly GameObject _tileGrid;
45 | [UIObject("tile-row")]
46 | private readonly GameObject _tileRow;
47 | [UIComponent("tile")]
48 | private readonly ClickableImage _tile;
49 |
50 | [UIObject("note-modal")]
51 | private readonly GameObject _noteModal;
52 | [UIObject("note-horizontal")]
53 | private readonly GameObject _noteHorizontal;
54 | [UIObject("note-grid")]
55 | private GameObject _noteGrid;
56 | [UIObject("note-row")]
57 | private GameObject _noteRow;
58 |
59 | [UIComponent("note")]
60 | private readonly ImageView _note;
61 | [UIComponent("note-dir-arrow")]
62 | private readonly ImageView _noteDirArrow;
63 | [UIComponent("note-cut-arrow")]
64 | private readonly ImageView _noteCutArrow;
65 | [UIComponent("note-cut-distance")]
66 | private readonly ImageView _noteCutDistance;
67 | [UIComponent("sd-version")]
68 | private readonly TextMeshProUGUI _sdVersionText;
69 | [UIComponent("reset-button")]
70 | private readonly RectTransform _resetButtonTransform;
71 |
72 | private List _tiles = new List();
73 | private List _notes = new List();
74 | private SelectedTileIndicator _selectedTileIndicator;
75 | private BasicUIAudioManager _basicUIAudioManager;
76 |
77 |
78 | [Inject]
79 | internal void Construct(SiraLog siraLog, AssetLoader assetLoader, HoverHintControllerHandler hoverHintControllerHandler, SliceProcessor sliceProcessor, DiContainer diContainer) {
80 | _siraLog = siraLog;
81 | _assetLoader = assetLoader;
82 | _hoverHintControllerHandler = hoverHintControllerHandler;
83 | _sliceProcessor = sliceProcessor;
84 | _diContainer = diContainer;
85 | _siraLog.Debug("GridViewController Constructed");
86 | }
87 |
88 | [UIAction("#post-parse")]
89 | public void PostParse() {
90 | _noteDirArrow.gameObject.name = "NoteDirArrow";
91 | _noteCutArrow.gameObject.name = "NoteCutArrow";
92 | _noteCutDistance.gameObject.name = "NoteCutDistance";
93 |
94 | _sdVersionText.text = $"SliceDetails v{ System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(3) }";
95 | ReflectionUtil.InvokeMethod