├── .gitattributes ├── .gitignore ├── PPPredictor.lutconfig ├── PPPredictor.sln ├── PPPredictor ├── Counter │ ├── CounterInfoHolder.cs │ ├── PPPCounter.cs │ └── Settings │ │ ├── CounterSettings.bsml │ │ └── CounterSettings.cs ├── Data │ ├── Curve │ │ ├── BeatLeaderPPPCurve.cs │ │ ├── CurveInfo.cs │ │ ├── CurveParser.cs │ │ └── CustomPPPCurve.cs │ ├── DisplayInfos │ │ ├── DisplayPPInfo.cs │ │ └── DisplaySessionInfo.cs │ ├── LeaderBoardDataTypes │ │ ├── AccSaberDataTypes.cs │ │ ├── BeatLeaderDataTypes.cs │ │ ├── HitBloqDataTypes.cs │ │ └── ScoreSaberDataTypes.cs │ ├── PPGainResult.cs │ ├── PPPBeatMapInfo.cs │ ├── PPPLeaderboardInfo.cs │ ├── PPPMapPool.cs │ ├── PPPMapPoolEntry.cs │ ├── PPPPlayer.cs │ ├── PPPScore.cs │ ├── PPPScoreCollection.cs │ ├── PPPStarRating.cs │ ├── PPPWebSocketData.cs │ ├── ProfileInfo.cs │ ├── RankGainResult.cs │ ├── SVector3.cs │ └── ShortScore.cs ├── Directory.Build.props ├── Installers │ ├── CoreInstaller.cs │ ├── GamePlayInstaller.cs │ ├── MainMenuInstaller.cs │ └── PPPPredictorDisplayInstaller.cs ├── Interfaces │ ├── IAccSaberAPI.cs │ ├── IBeatLeaderAPI.cs │ ├── IHitBloqAPI.cs │ ├── IPPPCurve.cs │ ├── IPPPRawWebsocketData.cs │ ├── IPPPWebSocket.cs │ ├── IPPPredictor.cs │ ├── IPPPredictorMgr.cs │ └── IScoresaberAPI.cs ├── OpenAPIs │ ├── PPPWebSocket.cs │ ├── accsaberapi.cs │ ├── beatleaderapi.cs │ ├── hitbloqapi.cs │ └── scoresaberapi.cs ├── OverlayServer │ └── WebSocketOverlayServer.cs ├── PPPredictor.csproj ├── Plugin.cs ├── Properties │ └── AssemblyInfo.cs ├── Resources │ ├── LeaderBoardLogos │ │ ├── AccSaber.png │ │ ├── BeatLeader.png │ │ ├── HitBloq.png │ │ └── ScoreSaber.png │ └── icon.png ├── UI │ ├── PPPredictorFlowCoordinator.cs │ ├── ViewController │ │ ├── PPPredictorViewController.cs │ │ └── SettingsMidViewController.cs │ └── Views │ │ ├── PPPredictorView.bsml │ │ └── SettingsMidView.bsml ├── Utilities │ ├── Constants.cs │ ├── DisplayHelper.cs │ ├── Enums.cs │ ├── GamePlayMgr.cs │ ├── MainMenuMgr.cs │ ├── PPCalculator.cs │ ├── PPCalculatorAccSaber.cs │ ├── PPCalculatorBeatLeader.cs │ ├── PPCalculatorHitBloq.cs │ ├── PPCalculatorNoLeaderboard.cs │ ├── PPCalculatorScoreSaber.cs │ ├── PPPredictor.cs │ ├── PPPredictorEventsMgr.cs │ ├── PPPredictorMgr.cs │ ├── ParsingUtil.cs │ ├── ProfileInfoMgr.cs │ └── WebSocketMgr.cs ├── VersionChecker │ ├── VersionChecker.cs │ └── VersionInfo.cs └── manifest.json ├── README.md ├── Starter ├── Program.cs └── Starter.csproj ├── UnitTests ├── Data │ ├── Curve │ │ ├── TestBeatLeaderPPPCurve.cs │ │ ├── TestCurveInfo.cs │ │ ├── TestCurveParser.cs │ │ └── TestCustomPPPCurve.cs │ ├── DisplayInfo │ │ ├── TestDisplayPPInfo.cs │ │ └── TestDisplaySessionInfo.cs │ ├── TestPPGainResult.cs │ ├── TestPPPBeatMapInfo.cs │ ├── TestPPPLeaderboardInfo.cs │ ├── TestPPPMapPool.cs │ ├── TestPPPMapPoolEntry.cs │ ├── TestPPPScore.cs │ ├── TestPPPScoreCollection.cs │ ├── TestPPPStarRating.cs │ ├── TestPPPlayer.cs │ ├── TestProfileInfo.cs │ ├── TestRankGainResult.cs │ ├── TestSVector3.cs │ └── TestShortScore.cs ├── MockServices │ └── MockScoreSaberApi.cs ├── TestUtils │ └── TestUtils.cs ├── UnitTests.csproj └── Usings.cs └── testEnvironments.json /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /PPPredictor.lutconfig: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | 180000 6 | -------------------------------------------------------------------------------- /PPPredictor.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33213.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PPPredictor", "PPPredictor\PPPredictor.csproj", "{7D72F6BF-5835-49C8-ADD7-8F2F587B9588}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Starter", "Starter\Starter.csproj", "{FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{8B7FB8F5-39D5-41E8-9583-1B8B7620D389}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug ACCSABERNETWORK|Any CPU = Debug ACCSABERNETWORK|Any CPU 15 | Debug ALLNETWORK|Any CPU = Debug ALLNETWORK|Any CPU 16 | Debug BEATLEADERNETWORK|Any CPU = Debug BEATLEADERNETWORK|Any CPU 17 | Debug HITBLOQNETWORK|Any CPU = Debug HITBLOQNETWORK|Any CPU 18 | Debug SCORESABERNETWORK|Any CPU = Debug SCORESABERNETWORK|Any CPU 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug ACCSABERNETWORK|Any CPU.ActiveCfg = Debug ACCSABERNETWORK|Any CPU 24 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug ACCSABERNETWORK|Any CPU.Build.0 = Debug ACCSABERNETWORK|Any CPU 25 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug ALLNETWORK|Any CPU.ActiveCfg = Debug ALLNETWORK|Any CPU 26 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug ALLNETWORK|Any CPU.Build.0 = Debug ALLNETWORK|Any CPU 27 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug BEATLEADERNETWORK|Any CPU.ActiveCfg = Debug BEATLEADERNETWORK|Any CPU 28 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug BEATLEADERNETWORK|Any CPU.Build.0 = Debug BEATLEADERNETWORK|Any CPU 29 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug HITBLOQNETWORK|Any CPU.ActiveCfg = Debug HITBLOQNETWORK|Any CPU 30 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug HITBLOQNETWORK|Any CPU.Build.0 = Debug HITBLOQNETWORK|Any CPU 31 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug SCORESABERNETWORK|Any CPU.ActiveCfg = Debug SCORESABERNETWORK|Any CPU 32 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug SCORESABERNETWORK|Any CPU.Build.0 = Debug SCORESABERNETWORK|Any CPU 33 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {7D72F6BF-5835-49C8-ADD7-8F2F587B9588}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug ACCSABERNETWORK|Any CPU.ActiveCfg = Debug ACCSABERNETWORK|Any CPU 38 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug ACCSABERNETWORK|Any CPU.Build.0 = Debug ACCSABERNETWORK|Any CPU 39 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug ALLNETWORK|Any CPU.ActiveCfg = Debug|Any CPU 40 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug ALLNETWORK|Any CPU.Build.0 = Debug|Any CPU 41 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug BEATLEADERNETWORK|Any CPU.ActiveCfg = Debug|Any CPU 42 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug BEATLEADERNETWORK|Any CPU.Build.0 = Debug|Any CPU 43 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug HITBLOQNETWORK|Any CPU.ActiveCfg = Debug|Any CPU 44 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug HITBLOQNETWORK|Any CPU.Build.0 = Debug|Any CPU 45 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug SCORESABERNETWORK|Any CPU.ActiveCfg = Debug|Any CPU 46 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug SCORESABERNETWORK|Any CPU.Build.0 = Debug|Any CPU 47 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {FA9C8BD1-3460-42AF-B129-1FD95E25A5B0}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug ACCSABERNETWORK|Any CPU.ActiveCfg = Debug|Any CPU 52 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug ACCSABERNETWORK|Any CPU.Build.0 = Debug|Any CPU 53 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug ALLNETWORK|Any CPU.ActiveCfg = Debug|Any CPU 54 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug ALLNETWORK|Any CPU.Build.0 = Debug|Any CPU 55 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug BEATLEADERNETWORK|Any CPU.ActiveCfg = Debug|Any CPU 56 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug BEATLEADERNETWORK|Any CPU.Build.0 = Debug|Any CPU 57 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug HITBLOQNETWORK|Any CPU.ActiveCfg = Debug|Any CPU 58 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug HITBLOQNETWORK|Any CPU.Build.0 = Debug|Any CPU 59 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug SCORESABERNETWORK|Any CPU.ActiveCfg = Debug|Any CPU 60 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug SCORESABERNETWORK|Any CPU.Build.0 = Debug|Any CPU 61 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {8B7FB8F5-39D5-41E8-9583-1B8B7620D389}.Release|Any CPU.Build.0 = Release|Any CPU 65 | EndGlobalSection 66 | GlobalSection(SolutionProperties) = preSolution 67 | HideSolutionNode = FALSE 68 | EndGlobalSection 69 | GlobalSection(ExtensibilityGlobals) = postSolution 70 | SolutionGuid = {222333A5-DD41-4ED5-8005-FECEBC7DA237} 71 | EndGlobalSection 72 | EndGlobal 73 | -------------------------------------------------------------------------------- /PPPredictor/Counter/Settings/CounterSettings.bsml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /PPPredictor/Counter/Settings/CounterSettings.cs: -------------------------------------------------------------------------------- 1 | using BeatSaberMarkupLanguage.Attributes; 2 | using PPPredictor.Utilities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | 7 | namespace PPPredictor.Counter.Settings 8 | { 9 | internal class CounterSettings : INotifyPropertyChanged 10 | { 11 | private readonly List scoringTypeOptions; 12 | private readonly List counterDisplayTypeOptions; 13 | public event PropertyChangedEventHandler PropertyChanged; 14 | 15 | public CounterSettings() 16 | { 17 | scoringTypeOptions = new List(); 18 | foreach (CounterScoringType enumValue in Enum.GetValues(typeof(CounterScoringType))) { 19 | scoringTypeOptions.Add(enumValue.ToString()); 20 | } 21 | counterDisplayTypeOptions = new List(); 22 | foreach (CounterDisplayType enumValue in Enum.GetValues(typeof(CounterDisplayType))) 23 | { 24 | counterDisplayTypeOptions.Add(EnumHelper.CounterDisplayTypeGetDisplayValue(enumValue)); 25 | } 26 | } 27 | 28 | [UIAction("#post-parse")] 29 | internal void PostParse() 30 | { 31 | // Code to run after BSML finishes 32 | } 33 | 34 | [UIValue("counter-display-type-options")] 35 | public List CounterDisplayTypeOptions 36 | { 37 | get => this.counterDisplayTypeOptions; 38 | } 39 | [UIValue("counter-display-type")] 40 | public string CounterDisplayType 41 | { 42 | get => EnumHelper.CounterDisplayTypeGetDisplayValue(Plugin.ProfileInfo.CounterDisplayType); 43 | set 44 | { 45 | Plugin.ProfileInfo.CounterDisplayType = EnumHelper.DisplayValueToCounterDisplayType(value); 46 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CounterDisplayType))); 47 | } 48 | } 49 | 50 | [UIValue("counter-use-icons")] 51 | public bool CounterUseIcons 52 | { 53 | get => Plugin.ProfileInfo.CounterUseIcons; 54 | set 55 | { 56 | Plugin.ProfileInfo.CounterUseIcons = value; 57 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CounterUseIcons))); 58 | } 59 | } 60 | [UIValue("counter-use-custom-mappool-icons")] 61 | public bool CounterUseCustomMapPoolIcons 62 | { 63 | get => Plugin.ProfileInfo.CounterUseCustomMapPoolIcons; 64 | set 65 | { 66 | Plugin.ProfileInfo.CounterUseCustomMapPoolIcons = value; 67 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CounterUseCustomMapPoolIcons))); 68 | } 69 | } 70 | [UIValue("counter-highlight-target-percentage")] 71 | public bool CounterHighlightTargetPercentage 72 | { 73 | get => Plugin.ProfileInfo.CounterHighlightTargetPercentage; 74 | set 75 | { 76 | Plugin.ProfileInfo.CounterHighlightTargetPercentage = value; 77 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CounterHighlightTargetPercentage))); 78 | } 79 | } 80 | [UIValue("counter-hide-when-unranked")] 81 | public bool CounterHideWhenUnranked 82 | { 83 | get => Plugin.ProfileInfo.CounterHideWhenUnranked; 84 | set 85 | { 86 | Plugin.ProfileInfo.CounterHideWhenUnranked = value; 87 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CounterHideWhenUnranked))); 88 | } 89 | } 90 | [UIValue("counter-scoring-type-options")] 91 | public List CounterScoringTypeOptions 92 | { 93 | get => this.scoringTypeOptions; 94 | } 95 | [UIValue("counter-scoring-type")] 96 | public string CounterScoringType 97 | { 98 | get => Plugin.ProfileInfo.CounterScoringType.ToString(); 99 | set 100 | { 101 | Plugin.ProfileInfo.CounterScoringType = (CounterScoringType)Enum.Parse(typeof(CounterScoringType), value); 102 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CounterScoringType))); 103 | } 104 | } 105 | [UIValue("counter-gain-silentmode")] 106 | public bool CounterGainSilentMode 107 | { 108 | get => Plugin.ProfileInfo.IsCounterGainSilentModeEnabled; 109 | set 110 | { 111 | Plugin.ProfileInfo.IsCounterGainSilentModeEnabled = value; 112 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CounterGainSilentMode))); 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /PPPredictor/Data/Curve/BeatLeaderPPPCurve.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Interfaces; 2 | using PPPredictor.Utilities; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace PPPredictor.Data.Curve 7 | { 8 | class BeatLeaderPPPCurve : IPPPCurve 9 | { 10 | private readonly List<(double, double)> accPointList = new List<(double, double)> { 11 | (1.0, 7.424), 12 | (0.999, 6.241), 13 | (0.9975, 5.158), 14 | (0.995, 4.010), 15 | (0.9925, 3.241), 16 | (0.99, 2.700), 17 | (0.9875, 2.303), 18 | (0.985, 2.007), 19 | (0.9825, 1.786), 20 | (0.98, 1.618), 21 | (0.9775, 1.490), 22 | (0.975, 1.392), 23 | (0.9725, 1.315), 24 | (0.97, 1.256), 25 | (0.965, 1.167), 26 | (0.96, 1.094), 27 | (0.955, 1.039), 28 | (0.95, 1.000), 29 | (0.94, 0.931), 30 | (0.93, 0.867), 31 | (0.92, 0.813), 32 | (0.91, 0.768), 33 | (0.9, 0.729), 34 | (0.875, 0.650), 35 | (0.85, 0.581), 36 | (0.825, 0.522), 37 | (0.8, 0.473), 38 | (0.75, 0.404), 39 | (0.7, 0.345), 40 | (0.65, 0.296), 41 | (0.6, 0.256), 42 | (0.0, 0.000), }; 43 | public bool IsDummy { get => false; } 44 | public double CalculatePPatPercentage(PPPBeatMapInfo beatMapInfo, double percentage, bool failed, bool paused, LeaderboardContext leaderboardContext = LeaderboardContext.None) 45 | { 46 | try 47 | { 48 | if (beatMapInfo.ModifiedStarRating.IsRanked()) 49 | { 50 | percentage /= 100.0; 51 | if (leaderboardContext == LeaderboardContext.BeatLeaderGolf) 52 | { 53 | if (percentage > 0.5f) return 0; 54 | percentage = 1f - percentage; 55 | } 56 | if (!failed && !(leaderboardContext == LeaderboardContext.BeatLeaderNoPauses && paused)) 57 | { 58 | var (passPP, accPP, techPP) = CalculatePP(percentage, beatMapInfo.ModifiedStarRating.AccRating * beatMapInfo.ModifiedStarRating.Multiplier, beatMapInfo.ModifiedStarRating.PassRating * beatMapInfo.ModifiedStarRating.Multiplier, beatMapInfo.ModifiedStarRating.TechRating * beatMapInfo.ModifiedStarRating.Multiplier, leaderboardContext); 59 | var rawPP = Inflate(passPP + accPP + techPP); 60 | return rawPP; 61 | } 62 | } 63 | return 0; 64 | } 65 | catch (Exception ex) 66 | { 67 | Plugin.ErrorPrint($"BeatLeaderPPPCurve CalculatePPatPercentage Error: {ex.Message}"); 68 | return -1; 69 | } 70 | } 71 | 72 | private (double, double, double) CalculatePP(double accuracy, double accRating, double passRating, double techRating, LeaderboardContext leaderboardContext = LeaderboardContext.None) 73 | { 74 | double passPP = 15.2f * Math.Exp(Math.Pow(passRating, 1 / 2.62f)) - 30f; 75 | if (double.IsInfinity(passPP) || double.IsNaN(passPP) || double.IsNegativeInfinity(passPP) || passPP < 0) 76 | { 77 | passPP = 0; 78 | } 79 | double accPP = leaderboardContext == LeaderboardContext.BeatLeaderGolf ? accuracy * accRating * 42f : AccCurve(accuracy) * accRating * 34f; 80 | double techPP = Math.Exp(1.9f * accuracy) * 1.08f * techRating; 81 | 82 | return (passPP, accPP, techPP); 83 | } 84 | 85 | private double AccCurve(double acc) 86 | { 87 | int i = 0; 88 | for (; i < accPointList.Count; i++) 89 | { 90 | if (accPointList[i].Item1 <= acc) 91 | { 92 | break; 93 | } 94 | } 95 | 96 | if (i == 0) 97 | { 98 | i = 1; 99 | } 100 | 101 | double middle_dis = (acc - accPointList[i - 1].Item1) / (accPointList[i].Item1 - accPointList[i - 1].Item1); 102 | return (accPointList[i - 1].Item2 + middle_dis * (accPointList[i].Item2 - accPointList[i - 1].Item2)); 103 | } 104 | 105 | private double Inflate(double pp) 106 | { 107 | return (650f * Math.Pow(pp, 1.3f)) / Math.Pow(650f, 1.3f); 108 | } 109 | 110 | public double CalculateMaxPP(PPPBeatMapInfo beatMapInfo, LeaderboardContext leaderboardContext = LeaderboardContext.None) 111 | { 112 | return CalculatePPatPercentage(beatMapInfo, 100, false, false, leaderboardContext); 113 | } 114 | 115 | public CurveInfo ToCurveInfo() 116 | { 117 | CurveInfo retCurve = new CurveInfo 118 | { 119 | CurveType = Utilities.CurveType.BeatLeader 120 | }; 121 | return retCurve; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /PPPredictor/Data/Curve/CurveInfo.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Utilities; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | 5 | namespace PPPredictor.Data.Curve 6 | { 7 | class CurveInfo 8 | { 9 | private CurveType _curveType; 10 | private List<(double, double)> _arrPPCurve; 11 | private double? basePPMultiplier; 12 | private double? baseline; 13 | private double? exponential; 14 | private double? cutoff; 15 | 16 | public List<(double, double)> ArrPPCurve { get => _arrPPCurve; set => _arrPPCurve = value; } 17 | [DefaultValue(null)] 18 | public double? BasePPMultiplier { get => basePPMultiplier; set => basePPMultiplier = value; } 19 | public CurveType CurveType { get => _curveType; set => _curveType = value; } 20 | [DefaultValue(null)] 21 | public double? Baseline { get => baseline; set => baseline = value; } 22 | [DefaultValue(null)] 23 | public double? Exponential { get => exponential; set => exponential = value; } 24 | [DefaultValue(null)] 25 | public double? Cutoff { get => cutoff; set => cutoff = value; } 26 | 27 | public CurveInfo() 28 | { 29 | } 30 | 31 | public CurveInfo(CurveType curveType) 32 | { 33 | _curveType = curveType; 34 | } 35 | 36 | public CurveInfo(CurveType curveType, List<(double, double)> arrPPCurve, double basePPMultiplier) 37 | { 38 | _curveType = curveType; 39 | _arrPPCurve = arrPPCurve; 40 | this.basePPMultiplier = basePPMultiplier; 41 | } 42 | 43 | public CurveInfo(CurveType curveType, List<(double, double)> arrPPCurve, double basePPMultiplier, double? baseline, double? exponential, double? cutoff) : this(curveType, arrPPCurve, basePPMultiplier) 44 | { 45 | this.baseline = baseline; 46 | this.exponential = exponential; 47 | this.cutoff = cutoff; 48 | } 49 | 50 | public override string ToString() 51 | { 52 | return $"CurveInfo: curveType {_curveType} - basePPMultiplier {basePPMultiplier.GetValueOrDefault()} _arrPPCurve {_arrPPCurve} baseline {baseline.GetValueOrDefault()} exponential {exponential.GetValueOrDefault()} cutoff {cutoff.GetValueOrDefault()}"; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /PPPredictor/Data/Curve/CurveParser.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Interfaces; 2 | using System.Collections.Generic; 3 | 4 | namespace PPPredictor.Data.Curve 5 | { 6 | class CurveParser 7 | { 8 | private static readonly double basePPMultiplierScoreSaber = 42.117208413; 9 | private static readonly List<(double, double)> arrPPCurveScoreSaber = new List<(double, double)>(new (double, double)[37] { 10 | (1.0, 5.367394282890631), 11 | (0.9995, 5.019543595874787), 12 | (0.999, 4.715470646416203), 13 | (0.99825, 4.325027383589547), 14 | (0.9975, 3.996793606763322), 15 | (0.99625, 3.5526145337555373), 16 | (0.995, 3.2022017597337955), 17 | (0.99375, 2.9190155639254955), 18 | (0.9925, 2.685667856592722), 19 | (0.99125, 2.4902905794106913), 20 | (0.99, 2.324506282149922), 21 | (0.9875, 2.058947159052738), 22 | (0.985, 1.8563887693647105), 23 | (0.9825, 1.697536248647543), 24 | (0.98, 1.5702410055532239), 25 | (0.9775, 1.4664726399289512), 26 | (0.975, 1.3807102743105126), 27 | (0.9725, 1.3090333065057616), 28 | (0.97, 1.2485807759957321), 29 | (0.965, 1.1552120359501035), 30 | (0.96, 1.0871883573850478), 31 | (0.955, 1.0388633331418984), 32 | (0.95, 1.0), 33 | (0.94, 0.9417362980580238), 34 | (0.93, 0.9039994071865736), 35 | (0.92, 0.8728710341448851), 36 | (0.91, 0.8488375988124467), 37 | (0.9, 0.825756123560842), 38 | (0.875, 0.7816934560296046), 39 | (0.85, 0.7462290664143185), 40 | (0.825, 0.7150465663454271), 41 | (0.8, 0.6872268862950283), 42 | (0.75, 0.6451808210101443), 43 | (0.7, 0.6125565959114954), 44 | (0.65, 0.5866010012767576), 45 | (0.6, 0.18223233667439062), 46 | (0.0, 0.0) }); 47 | private static readonly List<(double, double)> arrPPCurveAccSaber = new List<(double, double)>(new (double, double)[57] { 48 | (1, 1), 49 | (0.999798868015323, 1), 50 | (0.998801667612258, 0.881036259003904), 51 | (0.997597817448282, 0.807864970846212), 52 | (0.99638391362715, 0.75398939205533), 53 | (0.995192826072399, 0.711631856844816), 54 | (0.993982635397878, 0.675758283217714), 55 | (0.992777934371917, 0.645271361873838), 56 | (0.991572321617314, 0.618720490847344), 57 | (0.990361642931305, 0.595172789623826), 58 | (0.989158767583543, 0.574249679995056), 59 | (0.987946216049906, 0.555191831755976), 60 | (0.986753843546214, 0.538101879298717), 61 | (0.985534556510679, 0.522047289016481), 62 | (0.984334431588307, 0.507436997518391), 63 | (0.983122724803697, 0.493713431180267), 64 | (0.981930401543003, 0.481080710912171), 65 | (0.980712192241952, 0.468949988213597), 66 | (0.979514528967426, 0.457693261261058), 67 | (0.978307874266188, 0.446942167537732), 68 | (0.977108280868104, 0.436772954869709), 69 | (0.975901793480846, 0.427008348566038), 70 | (0.974689649744553, 0.417614336191856), 71 | (0.973487723323, 0.408667608162045), 72 | (0.972282442566079, 0.400025710310081), 73 | (0.971075080678785, 0.39166641973516), 74 | (0.9698668993297, 0.383569905983769), 75 | (0.968659134866764, 0.37571839822792), 76 | (0.967452986936859, 0.368095908419736), 77 | (0.96624960935703, 0.360687999490072), 78 | (0.965050103040447, 0.353481590071579), 79 | (0.963842342827163, 0.346388277643642), 80 | (0.96262795728598, 0.339405506964074), 81 | (0.961433998563471, 0.33267290950433), 82 | (0.96022316288647, 0.325967205856964), 83 | (0.95902216724236, 0.319426258587104), 84 | (0.957807698698665, 0.312913179773458), 85 | (0.956605438149408, 0.306556377051107), 86 | (0.955404451612776, 0.300287818697188), 87 | (0.954194718585367, 0.29404782746237), 88 | (0.952989233017565, 0.287896269816159), 89 | (0.951778352488454, 0.281776901969062), 90 | (0.950574437220297, 0.275745429410847), 91 | (0.949368212787461, 0.269748820334489), 92 | (0.948161368969195, 0.263789713483043), 93 | (0.946965275751128, 0.257918189085454), 94 | (0.945752195005795, 0.251992953632337), 95 | (0.9445528451823, 0.246159054011439), 96 | (0.943340858876901, 0.240283202722378), 97 | (0.942136498435854, 0.234458953551446), 98 | (0.940932458185028, 0.228646177461935), 99 | (0.939722349385998, 0.222809528908169), 100 | (0.938524585132052, 0.217033357984317), 101 | (0.937316865437844, 0.211205733205298), 102 | (0.936109641452604, 0.205372691403391), 103 | (0.934905010658402, 0.199540078052735), 104 | (0, 0) }); 105 | public static IPPPCurve ParseToCurve(CurveInfo curveInfo) 106 | { 107 | switch (curveInfo.CurveType) 108 | { 109 | case Utilities.CurveType.ScoreSaber: 110 | return new CustomPPPCurve(arrPPCurveScoreSaber, Utilities.CurveType.ScoreSaber, basePPMultiplierScoreSaber); 111 | case Utilities.CurveType.BeatLeader: 112 | return new BeatLeaderPPPCurve(); 113 | case Utilities.CurveType.Linear: 114 | return new CustomPPPCurve(curveInfo.ArrPPCurve, Utilities.CurveType.Linear, curveInfo.BasePPMultiplier.GetValueOrDefault()); 115 | case Utilities.CurveType.Basic: 116 | return CustomPPPCurve.CreateBasicPPPCurve(curveInfo.BasePPMultiplier.GetValueOrDefault(), curveInfo.Baseline, curveInfo.Exponential, curveInfo.Cutoff); 117 | case Utilities.CurveType.AccSaber: 118 | return new CustomPPPCurve(arrPPCurveAccSaber, Utilities.CurveType.AccSaber, 61, starOffest: 18); 119 | default: 120 | return CustomPPPCurve.CreateDummyPPPCurve(); 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /PPPredictor/Data/DisplayInfos/DisplayPPInfo.cs: -------------------------------------------------------------------------------- 1 | namespace PPPredictor.Data.DisplayInfos 2 | { 3 | internal class DisplayPPInfo 4 | { 5 | internal string PPRaw { get; set; } = string.Empty; 6 | internal string PPGain { get; set; } = string.Empty; 7 | internal string PPGainDiffColor { get; set; } = string.Empty; 8 | internal string PredictedRank { get; set; } = string.Empty; 9 | internal string PredictedRankDiff { get; set; } = string.Empty; 10 | internal string PredictedRankDiffColor { get; set; } = string.Empty; 11 | internal string PredictedCountryRank { get; set; } = string.Empty; 12 | internal string PredictedCountryRankDiff { get; set; } = string.Empty; 13 | internal string PredictedCountryRankDiffColor { get; set; } = string.Empty; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PPPredictor/Data/DisplayInfos/DisplaySessionInfo.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Utilities; 2 | 3 | namespace PPPredictor.Data.DisplayInfos 4 | { 5 | class DisplaySessionInfo 6 | { 7 | internal string SessionRank { get; set; } = "-"; 8 | internal string SessionRankDiff { get; set; } = "-"; 9 | internal string SessionRankDiffColor { get; set; } = DisplayHelper.ColorWhite; 10 | internal string SessionCountryRank { get; set; } = "-"; 11 | internal string SessionCountryRankDiff { get; set; } = "-"; 12 | internal string SessionCountryRankDiffColor { get; set; } = DisplayHelper.ColorWhite; 13 | internal string CountryRankFontColor { get; set; } = DisplayHelper.ColorWhite; 14 | internal string SessionPP { get; set; } = "-"; 15 | internal string SessionPPDiff { get; set; } = "-"; 16 | internal string SessionPPDiffColor { get; set; } = DisplayHelper.ColorWhite; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PPPredictor/Data/LeaderBoardDataTypes/AccSaberDataTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PPPredictor.Data.LeaderBoardDataTypes 4 | { 5 | internal class AccSaberDataTypes 6 | { 7 | internal class AccSaberScores 8 | { 9 | public double ap { get; set; } 10 | public double weightedAp { get; set; } 11 | public string songHash { get; set; } 12 | public string difficulty { get; set; } 13 | public DateTime timeSet { get; set; } 14 | public string categoryDisplayName { get; set; } 15 | } 16 | 17 | internal class AccSaberPlayer 18 | { 19 | public int rank { get; set; } 20 | public double ap { get; set; } 21 | } 22 | 23 | internal class AccSaberMapPool 24 | { 25 | public string categoryName { get; set; } 26 | public string categoryDisplayName { get; set; } 27 | } 28 | 29 | internal class AccSaberRankedMap 30 | { 31 | public string difficulty { get; set; } 32 | public string songHash { get; set; } 33 | public double complexity { get; set; } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /PPPredictor/Data/LeaderBoardDataTypes/BeatLeaderDataTypes.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Utilities; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace PPPredictor.Data.LeaderBoardDataTypes 6 | { 7 | internal class BeatLeaderDataTypes 8 | { 9 | #pragma warning disable IDE1006 // Naming Styles; api dictates them... 10 | public class BeatLeaderEventList 11 | { 12 | public List data { get; set; } 13 | public BeatLeaderEventList() 14 | { 15 | data = new List(); 16 | } 17 | } 18 | 19 | public class BeatLeaderEvent 20 | { 21 | public int id { get; set; } 22 | public string name { get; set; } 23 | public long endDate { get; set; } 24 | public DateTime dtEndDate 25 | { 26 | get 27 | { 28 | if (long.TryParse(endDate.ToString(), out long timeSetLong)) 29 | { 30 | return new DateTime(1970, 1, 1).AddSeconds(timeSetLong); 31 | } 32 | return new DateTime(1970, 1, 1); 33 | } 34 | } 35 | public long playListId { get; set; } 36 | } 37 | 38 | public class BeatLeaderSong 39 | { 40 | public List difficulties { get; set; } 41 | public string hash { get; set; } 42 | public BeatLeaderSong() 43 | { 44 | difficulties = new List(); 45 | } 46 | } 47 | 48 | public class BeatLeaderDifficulty 49 | { 50 | public int value { get; set; } 51 | public double? stars { get; set; } 52 | public double? predictedAcc { get; set; } 53 | public double? passRating { get; set; } 54 | public double? accRating { get; set; } 55 | public double? techRating { get; set; } 56 | public int status { get; set; } 57 | public string modeName { get; set; } 58 | public Dictionary modifierValues { get; set; } 59 | public Dictionary modifiersRating { get; set; } 60 | } 61 | 62 | public class BeatLeaderPlayerList 63 | { 64 | public List data; 65 | public BeatLeaderPlayerList() 66 | { 67 | data = new List(); 68 | } 69 | } 70 | 71 | public class BeatLeaderPlayer 72 | { 73 | public string name { get; set; } 74 | public string country { get; set; } 75 | public float rank { get; set; } 76 | public float countryRank { get; set; } 77 | public float pp { get; set; } 78 | public List eventsParticipating { get; set; } 79 | } 80 | 81 | public class BeatLeaderPlayerEvents 82 | { 83 | public long id { get; set; } 84 | public long eventId { get; set; } 85 | public string name { get; set; } 86 | public string country { get; set; } 87 | public float rank { get; set; } 88 | public float countryRank { get; set; } 89 | public float pp { get; set; } 90 | } 91 | 92 | public class BeatLeaderPlayerScoreList 93 | { 94 | public List data { get; set; } 95 | public BeatLeaderPlayerScoreListMetaData metadata { get; set; } 96 | public BeatLeaderPlayerScoreList() 97 | { 98 | metadata = new BeatLeaderPlayerScoreListMetaData(); 99 | data = new List(); 100 | } 101 | } 102 | 103 | public class BeatLeaderPlayerScore 104 | { 105 | public string timeset { get; set; } 106 | public float pp { get; set; } 107 | public BeatLeaderLeaderboard leaderboard { get; set; } = new BeatLeaderLeaderboard(); 108 | } 109 | 110 | public class BeatLeaderPlayerScoreListMetaData 111 | { 112 | public int itemsPerPage { get; set; } 113 | public int page { get; set; } 114 | public int total { get; set; } 115 | } 116 | 117 | public class BeatLeaderLeaderboard 118 | { 119 | public string hash { get; set; } 120 | public BeatLeaderDifficulty difficulty { get; set; } = new BeatLeaderDifficulty(); 121 | public BeatLeaderSong song { get; set; } = new BeatLeaderSong(); 122 | } 123 | 124 | public class BeatLeaderPlayList 125 | { 126 | public List songs { get; set; } 127 | public BeatLeaderPlayList() 128 | { 129 | songs = new List(); 130 | } 131 | 132 | } 133 | 134 | public class BeatLeaderPlayListSong 135 | { 136 | public string hash { get; set; } 137 | public List difficulties { get; set; } 138 | public BeatLeaderPlayListSong() 139 | { 140 | difficulties = new List(); 141 | } 142 | } 143 | 144 | 145 | public class BeatLeaderPlayListDifficulties 146 | { 147 | public PPPBeatMapDifficulty name { get; set; } 148 | public string characteristic { get; set; } 149 | } 150 | } 151 | #pragma warning restore IDE1006 // Naming Styles; api dictates them... 152 | } 153 | -------------------------------------------------------------------------------- /PPPredictor/Data/LeaderBoardDataTypes/HitBloqDataTypes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | 4 | namespace PPPredictor.Data.LeaderBoardDataTypes 5 | { 6 | internal class HitBloqDataTypes 7 | { 8 | #pragma warning disable IDE1006 // Naming Styles; api dictates them... 9 | public class HitBloqUserId 10 | { 11 | public long id { get; set; } 12 | } 13 | 14 | public class HitBloqMapPool 15 | { 16 | public string id { get; set; } 17 | public string title { get; set; } 18 | public string short_description { get; set; } 19 | public string image { get; set; } 20 | public int popularity { get; set; } 21 | public string download_url { get; set; } 22 | } 23 | 24 | public class HitBloqMapPoolDetails 25 | { 26 | public float accumulation_constant { get; set; } 27 | public HitBloqCrCurve cr_curve { get; set; } 28 | public List leaderboard_id_list { get; set; } 29 | public string long_description { get; set; } 30 | } 31 | 32 | public class HitBloqCrCurve 33 | { 34 | public List points { get; set; } 35 | public string type { get; set; } 36 | [DefaultValue(null)] 37 | public double? baseline { get; set; } 38 | [DefaultValue(null)] 39 | public double? exponential { get; set; } 40 | [DefaultValue(null)] 41 | public double? cutoff { get; set; } 42 | } 43 | 44 | public class HitBloqUser 45 | { 46 | public float cr { get; set; } 47 | public int rank { get; set; } 48 | public string username { get; set; } 49 | } 50 | 51 | public class HitBloqScores 52 | { 53 | public float cr_received { get; set; } 54 | public string song_id { get; set; } 55 | public long time { get; set; } 56 | } 57 | 58 | public class HitBloqLadder 59 | { 60 | public List ladder { get; set; } 61 | } 62 | 63 | public class HitBloqRankFromCr 64 | { 65 | public int rank { get; set; } 66 | } 67 | 68 | public class HitBloqLeaderboardInfo 69 | { 70 | public Dictionary star_rating { get; set; } 71 | } 72 | #pragma warning restore IDE1006 // Naming Styles 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /PPPredictor/Data/LeaderBoardDataTypes/ScoreSaberDataTypes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PPPredictor.Data.LeaderBoardDataTypes 4 | { 5 | internal class ScoreSaberDataTypes 6 | { 7 | #pragma warning disable IDE1006 // Naming Styles; api dictates them... 8 | public class ScoreSaberPlayerList 9 | { 10 | public List players { get; set; } 11 | 12 | public ScoreSaberPlayerList() 13 | { 14 | players = new List(); 15 | } 16 | } 17 | 18 | public class ScoreSaberPlayer 19 | { 20 | public string country { get; set; } 21 | public double pp { get; set; } 22 | public double rank { get; set; } 23 | public double countryRank { get; set; } 24 | } 25 | 26 | public class ScoreSaberPlayerScoreList 27 | { 28 | public List playerScores { get; set; } 29 | public ScoreSaberScoreListMetadata metadata { get; set; } 30 | } 31 | 32 | public class ScoreSaberScoreListMetadata 33 | { 34 | public double total { get; set; } 35 | public double page { get; set; } 36 | public double itemsPerPage { get; set; } 37 | } 38 | 39 | public class ScoreSaberPlayerScore 40 | { 41 | public ScoreSaberScore score { get; set; } = new ScoreSaberScore(); 42 | public ScoreSaberLeaderboardInfo leaderboard { get; set; } = new ScoreSaberLeaderboardInfo(); 43 | } 44 | 45 | public class ScoreSaberLeaderboardInfo 46 | { 47 | public string songHash { get; set; } 48 | public ScoreSaberDifficulty difficulty { get; set; } = new ScoreSaberDifficulty(); 49 | } 50 | 51 | public class ScoreSaberDifficulty 52 | { 53 | public double difficulty { get; set; } 54 | public string gameMode { get; set; } 55 | } 56 | 57 | public class ScoreSaberScore 58 | { 59 | public double pp { get; set; } 60 | public System.DateTimeOffset timeSet { get; set; } 61 | } 62 | #pragma warning restore IDE1006 // Naming Styles; api dictates them... 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /PPPredictor/Data/PPGainResult.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Utilities; 2 | namespace PPPredictor.Data 3 | { 4 | class PPGainResult 5 | { 6 | private readonly double _ppTotal; 7 | private readonly double _ppGainWeighted; 8 | private readonly double _ppGainRaw; 9 | 10 | public double PpTotal { get => _ppTotal; } 11 | public double PpGainWeighted { get => _ppGainWeighted; } 12 | public double PpGainRaw { get => _ppGainRaw; } 13 | 14 | public PPGainResult() : this(0,0,0) { } 15 | 16 | public PPGainResult(double ppTotal, double ppGainWeighted, double ppGainRaw) 17 | { 18 | _ppTotal = ppTotal; 19 | _ppGainWeighted = ppGainWeighted; 20 | _ppGainRaw = ppGainRaw; 21 | } 22 | 23 | internal double GetDisplayPPValue() 24 | { 25 | double retValue = 0; 26 | switch (Plugin.ProfileInfo.PpGainCalculationType) 27 | { 28 | case PPGainCalculationType.Weighted: 29 | retValue = PpGainWeighted; 30 | break; 31 | case PPGainCalculationType.Raw: 32 | retValue = PpGainRaw; 33 | break; 34 | } 35 | return retValue; 36 | } 37 | 38 | public override string ToString() 39 | { 40 | return $"PPGainResult: PpTotal {PpTotal} PpGainWeighted {PpGainWeighted} PpGainRaw {PpGainRaw} GetDisplayPPValue {GetDisplayPPValue()}"; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PPPredictor/Data/PPPBeatMapInfo.cs: -------------------------------------------------------------------------------- 1 | namespace PPPredictor.Data 2 | { 3 | class PPPBeatMapInfo 4 | { 5 | PPPStarRating _baseStarRating = new PPPStarRating(); 6 | PPPStarRating _modifiedStarRating = new PPPStarRating(); 7 | CustomBeatmapLevel _selectedCustomBeatmapLevel; 8 | IDifficultyBeatmap _beatmap; 9 | double _maxPP = -1; 10 | string _selectedMapSearchString; 11 | bool _oldDotsEnabled; 12 | 13 | public PPPStarRating BaseStarRating { get => _baseStarRating; set => _baseStarRating = value; } 14 | internal PPPStarRating ModifiedStarRating { get => _modifiedStarRating; set => _modifiedStarRating = value; } 15 | public CustomBeatmapLevel SelectedCustomBeatmapLevel { get => _selectedCustomBeatmapLevel; set => _selectedCustomBeatmapLevel = value; } 16 | public IDifficultyBeatmap Beatmap { get => _beatmap; set => _beatmap = value; } 17 | public double MaxPP { get => _maxPP; set => _maxPP = value; } 18 | public string SelectedMapSearchString { get => _selectedMapSearchString; set => _selectedMapSearchString = value; } 19 | public bool OldDotsEnabled { get => _oldDotsEnabled; set => _oldDotsEnabled = value; } 20 | 21 | public PPPBeatMapInfo() 22 | { 23 | _baseStarRating = _modifiedStarRating = new PPPStarRating(); 24 | } 25 | 26 | public PPPBeatMapInfo(PPPBeatMapInfo beatMapInfo, PPPStarRating baseStars) : this(beatMapInfo.SelectedCustomBeatmapLevel, beatMapInfo.Beatmap) 27 | { 28 | _baseStarRating = _modifiedStarRating = baseStars; 29 | } 30 | 31 | public PPPBeatMapInfo(CustomBeatmapLevel selectedCustomBeatmapLevel, IDifficultyBeatmap beatmap) 32 | { 33 | _selectedCustomBeatmapLevel = selectedCustomBeatmapLevel; 34 | this._beatmap = beatmap; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PPPredictor/Data/PPPLeaderboardInfo.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Data.Curve; 2 | using PPPredictor.OpenAPIs; 3 | using PPPredictor.Utilities; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace PPPredictor.Data 8 | { 9 | class PPPLeaderboardInfo 10 | { 11 | private List _lsMapPools; 12 | private string _leaderboardName; 13 | private string _leaderboardIcon; 14 | private PPPMapPool _currentMapPool; 15 | private string _lastSelectedMapPoolId; 16 | private string _customLeaderboardUserId; 17 | private string _ppSuffix; 18 | private int _leaderboardFirstPageIndex; 19 | private bool _isCountryRankEnabled; 20 | private int _largePageSize; 21 | 22 | public string LeaderboardName { get => _leaderboardName; set => _leaderboardName = value; } 23 | public string LeaderboardIcon { get => _leaderboardIcon; set => _leaderboardIcon = value; } 24 | public List LsMapPools { get => _lsMapPools; set => _lsMapPools = value; } 25 | public string LastSelectedMapPoolId { get => _lastSelectedMapPoolId; set => _lastSelectedMapPoolId = value; } 26 | internal PPPMapPool CurrentMapPool { get => _currentMapPool; set 27 | { 28 | _currentMapPool = value; 29 | LastSelectedMapPoolId = value.Id; 30 | } 31 | } 32 | internal PPPMapPool DefaultMapPool { get => _lsMapPools.Find(x => x.MapPoolType == MapPoolType.Default); } 33 | public string CustomLeaderboardUserId { get => _customLeaderboardUserId; set => _customLeaderboardUserId = value; } 34 | public string PpSuffix { get => _ppSuffix; set => _ppSuffix = value; } 35 | public int LeaderboardFirstPageIndex { get => _leaderboardFirstPageIndex; set => _leaderboardFirstPageIndex = value; } 36 | public bool IsCountryRankEnabled { get => _isCountryRankEnabled; set => _isCountryRankEnabled = value; } 37 | public int LargePageSize { get => _largePageSize; set => _largePageSize = value; } 38 | 39 | public PPPLeaderboardInfo() 40 | { 41 | } 42 | 43 | public PPPLeaderboardInfo(Leaderboard leaderboard) 44 | { 45 | this._leaderboardName = leaderboard.ToString(); 46 | this._lsMapPools = new List(); 47 | this._customLeaderboardUserId = string.Empty; 48 | this._ppSuffix = "pp"; 49 | this.LeaderboardFirstPageIndex = 1; 50 | this.IsCountryRankEnabled = true; 51 | this.LargePageSize = 10; 52 | 53 | switch (leaderboard) 54 | { 55 | case Leaderboard.ScoreSaber: 56 | _leaderboardIcon = "PPPredictor.Resources.LeaderBoardLogos.ScoreSaber.png"; 57 | _lsMapPools.Add(new PPPMapPool(MapPoolType.Default, $"", PPCalculatorScoreSaber.accumulationConstant, 0, CurveParser.ParseToCurve(new CurveInfo(CurveType.ScoreSaber)))); 58 | break; 59 | case Leaderboard.BeatLeader: 60 | _leaderboardIcon = "PPPredictor.Resources.LeaderBoardLogos.BeatLeader.png"; 61 | //Negative ids for distinguishing from possible future event map pools 62 | _lsMapPools.Add(new PPPMapPool("-1", MapPoolType.Default, $"General", PPCalculatorBeatLeader.accumulationConstant, 0, new BeatLeaderPPPCurve(), LeaderboardContext.BeatLeaderDefault)); 63 | _lsMapPools.Add(new PPPMapPool("-2", MapPoolType.Default, $"No modifiers", PPCalculatorBeatLeader.accumulationConstant, 0, new BeatLeaderPPPCurve(), LeaderboardContext.BeatLeaderNoModifiers)); 64 | _lsMapPools.Add(new PPPMapPool("-3", MapPoolType.Default, $"No pauses", PPCalculatorBeatLeader.accumulationConstant, 0, new BeatLeaderPPPCurve(), LeaderboardContext.BeatLeaderNoPauses)); 65 | _lsMapPools.Add(new PPPMapPool("-4", MapPoolType.Default, $"Golf", PPCalculatorBeatLeader.accumulationConstant, 0, new BeatLeaderPPPCurve(), LeaderboardContext.BeatLeaderGolf)); 66 | _lsMapPools.Add(new PPPMapPool("-5", MapPoolType.Default, $"SCPM", PPCalculatorBeatLeader.accumulationConstant, 0, new BeatLeaderPPPCurve(), LeaderboardContext.BeatLeaderSCPM)); 67 | this.LargePageSize = 100; 68 | break; 69 | case Leaderboard.NoLeaderboard: 70 | _leaderboardIcon = ""; 71 | _lsMapPools.Add(new PPPMapPool(MapPoolType.Default, $"", 0, 0, new CustomPPPCurve(new List<(double, double)>(), CurveType.Linear, 0))); 72 | break; 73 | case Leaderboard.HitBloq: 74 | _leaderboardIcon = "PPPredictor.Resources.LeaderBoardLogos.HitBloq.png"; 75 | _ppSuffix = "cr"; 76 | IsCountryRankEnabled = false; 77 | LeaderboardFirstPageIndex = 0; 78 | _lsMapPools.Add(new PPPMapPool(MapPoolType.Default, $"☞ Select a map pool ☜", 0, 0, new CustomPPPCurve(new List<(double, double)>(), CurveType.Linear, 0))); 79 | break; 80 | case Leaderboard.AccSaber: 81 | _leaderboardIcon = "PPPredictor.Resources.LeaderBoardLogos.AccSaber.png"; 82 | _ppSuffix = "ap"; 83 | IsCountryRankEnabled = false; 84 | LeaderboardFirstPageIndex = 0; 85 | _lsMapPools.Add(new PPPMapPool(MapPoolType.Default, $"☞ Select a map pool ☜", 0, 0, CurveParser.ParseToCurve(new CurveInfo(CurveType.AccSaber)), 0)); 86 | break; 87 | } 88 | 89 | //THIS IS DEBUG STUFF FOR NOW 90 | SetCurrentMapPool(); 91 | } 92 | 93 | internal void SetCurrentMapPool() 94 | { 95 | if (_lsMapPools != null && _lsMapPools.Count > 0) 96 | { 97 | int index = Math.Max(_lsMapPools.FindIndex(x => x.Id == LastSelectedMapPoolId), 0); //Set the last used map pool 98 | this._currentMapPool = _lsMapPools[index]; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /PPPredictor/Data/PPPMapPool.cs: -------------------------------------------------------------------------------- 1 | using BeatSaberMarkupLanguage.Attributes; 2 | using Newtonsoft.Json; 3 | using PPPredictor.Data.Curve; 4 | using PPPredictor.Interfaces; 5 | using PPPredictor.Utilities; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | namespace PPPredictor.Data 11 | { 12 | class PPPMapPool 13 | { 14 | private string _id; 15 | private string _playListId; 16 | private MapPoolType _mapPoolType; 17 | private string _mapPoolName; 18 | private float _accumulationConstant; 19 | private int _sortIndex; 20 | private IPPPCurve _curve; 21 | private PPPPlayer _sessionPlayer; 22 | private PPPPlayer _currentPlayer; 23 | private List _lsScores; 24 | private List _lsLeaderboardInfo; 25 | private List _lsMapPoolEntries; 26 | private List _lsPlayerRankings; 27 | private DateTime _dtUtcLastRefresh; 28 | private DateTimeOffset _dtLastScoreSet; 29 | private double _popularity; 30 | private string _iconUrl; 31 | private byte[] _iconData; 32 | private string _syncUrl; 33 | private LeaderboardContext _leaderboardContext; 34 | private bool isPlayerFound; 35 | private Dictionary dctWeightLookup; 36 | 37 | [UIValue("mapPoolName")] 38 | public string MapPoolName { get => _mapPoolName; set => _mapPoolName = value; } 39 | public float AccumulationConstant { get => _accumulationConstant; set => _accumulationConstant = value; } 40 | public int SortIndex { get => _sortIndex; set => _sortIndex = value; } 41 | public List LsScores 42 | { 43 | get => _lsScores; 44 | set 45 | { 46 | if (value != null) 47 | _lsScores = value.OrderByDescending(x => x.Pp).ToList(); 48 | else 49 | _lsScores = value; 50 | } 51 | } 52 | public List LsLeaderboadInfo { get => _lsLeaderboardInfo; set => _lsLeaderboardInfo = value; } 53 | public List LsMapPoolEntries { get => _lsMapPoolEntries; set => _lsMapPoolEntries = value; } 54 | public MapPoolType MapPoolType { get => _mapPoolType; set => _mapPoolType = value; } 55 | internal IPPPCurve Curve { get => _curve; set => _curve = value; } 56 | public CurveInfo CurveInfo { get => _curve.IsDummy ? null : _curve.ToCurveInfo(); set => _curve = CurveParser.ParseToCurve(value); } 57 | public PPPPlayer SessionPlayer { get => _sessionPlayer; set => _sessionPlayer = value; } 58 | public PPPPlayer CurrentPlayer { get => _currentPlayer; set => _currentPlayer = value; } 59 | public string Id { get => _id; set => _id = value; } 60 | public string PlayListId { get => _playListId; set => _playListId = value; } 61 | [JsonIgnore] 62 | public List LsPlayerRankings { get => _lsPlayerRankings; set => _lsPlayerRankings = value; } 63 | public DateTime DtUtcLastRefresh { get => _dtUtcLastRefresh; set => _dtUtcLastRefresh = value; } 64 | public DateTimeOffset DtLastScoreSet { get => _dtLastScoreSet; set => _dtLastScoreSet = value; } 65 | public string IconUrl { get => _iconUrl; set => _iconUrl = value; } 66 | [JsonIgnore] 67 | public byte[] IconData { get => _iconData; set => _iconData = value; } 68 | public double Popularity { get => _popularity; set => _popularity = value; } 69 | public string SyncUrl { get => _syncUrl; set => _syncUrl = value; } 70 | public LeaderboardContext LeaderboardContext { get => _leaderboardContext; set => _leaderboardContext = value; } 71 | public bool IsPlayerFound { get => isPlayerFound; set => isPlayerFound = value; } 72 | public Dictionary DctWeightLookup { get => dctWeightLookup; set => dctWeightLookup = value; } 73 | 74 | [JsonConstructor] 75 | 76 | public PPPMapPool() 77 | { 78 | _currentPlayer = new PPPPlayer(); 79 | _lsScores = new List(); 80 | LsLeaderboadInfo = new List(); 81 | _lsMapPoolEntries = new List(); 82 | _lsPlayerRankings = new List(); 83 | _dtUtcLastRefresh = new DateTime(2000, 1, 1); 84 | _curve = CustomPPPCurve.CreateDummyPPPCurve(); 85 | _id = "-1"; 86 | _playListId = "-1"; 87 | _mapPoolType = MapPoolType.Custom; 88 | _mapPoolName = string.Empty; 89 | _accumulationConstant = 0; 90 | _sortIndex = -1; 91 | _dtLastScoreSet = new DateTime(2000, 1, 1); 92 | _popularity = 0; 93 | _syncUrl = string.Empty; 94 | _leaderboardContext = LeaderboardContext.None; 95 | isPlayerFound = true; 96 | dctWeightLookup = new Dictionary(); 97 | } 98 | 99 | public PPPMapPool(string id, string playListId, MapPoolType mapPoolType, string mapPoolName, float accumulationConstant, int sortIndex, IPPPCurve curve, string iconUrl, double popularity = 0, string syncUrl = "", LeaderboardContext leaderboardContext = LeaderboardContext.None) : this() 100 | { 101 | _id = id; 102 | _playListId = playListId; 103 | _mapPoolType = mapPoolType; 104 | _mapPoolName = mapPoolName; 105 | _accumulationConstant = accumulationConstant; 106 | _sortIndex = sortIndex; 107 | _curve = curve; 108 | _iconUrl= iconUrl; 109 | _popularity = popularity; 110 | _syncUrl= syncUrl; 111 | _leaderboardContext = leaderboardContext; 112 | } 113 | 114 | public PPPMapPool(MapPoolType mapPoolType, string mapPoolName, float accumulationConstant, int sortIndex, IPPPCurve curve, LeaderboardContext leaderboardContext = LeaderboardContext.None) : this("-1", "-1", mapPoolType, mapPoolName, accumulationConstant, sortIndex, curve, string.Empty, 0, "", leaderboardContext) 115 | { 116 | } 117 | 118 | public PPPMapPool(string id, MapPoolType mapPoolType, string mapPoolName, float accumulationConstant, int sortIndex, IPPPCurve curve, LeaderboardContext leaderboardContext = LeaderboardContext.None) : this(id, "-1", mapPoolType, mapPoolName, accumulationConstant, sortIndex, curve, string.Empty, 0, "", leaderboardContext) 119 | { 120 | } 121 | 122 | public override string ToString() 123 | { 124 | return $"{_mapPoolName}"; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /PPPredictor/Data/PPPMapPoolEntry.cs: -------------------------------------------------------------------------------- 1 | using static PPPredictor.Data.LeaderBoardDataTypes.BeatLeaderDataTypes; 2 | 3 | namespace PPPredictor.Data 4 | { 5 | class PPPMapPoolEntry 6 | { 7 | private string _searchstring; 8 | public string Searchstring { get => _searchstring; set => _searchstring = value; } 9 | 10 | public PPPMapPoolEntry() 11 | { 12 | Searchstring = string.Empty; 13 | } 14 | public PPPMapPoolEntry(string searchstring) 15 | { 16 | Searchstring = searchstring; 17 | } 18 | 19 | public PPPMapPoolEntry(BeatLeaderPlayListSong song, BeatLeaderPlayListDifficulties diff) 20 | { 21 | _searchstring = $"{song.hash}_{(int)diff.name}"; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PPPredictor/Data/PPPPlayer.cs: -------------------------------------------------------------------------------- 1 | using static PPPredictor.Data.LeaderBoardDataTypes.AccSaberDataTypes; 2 | using static PPPredictor.Data.LeaderBoardDataTypes.BeatLeaderDataTypes; 3 | using static PPPredictor.Data.LeaderBoardDataTypes.HitBloqDataTypes; 4 | using static PPPredictor.Data.LeaderBoardDataTypes.ScoreSaberDataTypes; 5 | 6 | namespace PPPredictor.Data 7 | { 8 | class PPPPlayer 9 | { 10 | private double rank; 11 | private double countryRank; 12 | private double pp; 13 | private string country; 14 | private readonly bool isErrorUser = false; 15 | 16 | public double Rank { get => rank; set => rank = value; } 17 | public double CountryRank { get => countryRank; set => countryRank = value; } 18 | public double Pp { get => pp; set => pp = value; } 19 | public string Country { get => country; set => country = value; } 20 | public bool IsErrorUser { get => isErrorUser; } 21 | 22 | public PPPPlayer() 23 | { 24 | rank = countryRank = pp = 0; 25 | } 26 | public PPPPlayer(bool isErrorUser) 27 | { 28 | this.isErrorUser = isErrorUser; 29 | rank = countryRank = pp = 0; 30 | } 31 | public PPPPlayer(ScoreSaberPlayer scoreSaberPlayer) 32 | { 33 | rank = scoreSaberPlayer.rank; 34 | countryRank = scoreSaberPlayer.countryRank; 35 | pp = scoreSaberPlayer.pp; 36 | country = scoreSaberPlayer.country; 37 | } 38 | 39 | public PPPPlayer(BeatLeaderPlayer beatLeaderPlayerEvent) 40 | { 41 | rank = beatLeaderPlayerEvent.rank; 42 | countryRank = beatLeaderPlayerEvent.countryRank; 43 | pp = beatLeaderPlayerEvent.pp; 44 | country = beatLeaderPlayerEvent.country; 45 | } 46 | 47 | public PPPPlayer(BeatLeaderPlayerEvents beatLeaderPlayerEvent) 48 | { 49 | rank = beatLeaderPlayerEvent.rank; 50 | countryRank = beatLeaderPlayerEvent.countryRank; 51 | pp = beatLeaderPlayerEvent.pp; 52 | country = beatLeaderPlayerEvent.country; 53 | } 54 | 55 | public PPPPlayer(HitBloqUser hitBloqUser) 56 | { 57 | rank = hitBloqUser.rank; 58 | countryRank = 0; 59 | pp = hitBloqUser.cr; 60 | country = string.Empty; 61 | } 62 | 63 | public PPPPlayer(AccSaberPlayer accSaberPlayer) 64 | { 65 | rank = accSaberPlayer.rank; 66 | countryRank = 0; 67 | pp = accSaberPlayer.ap; 68 | country = string.Empty; 69 | } 70 | 71 | public override string ToString() 72 | { 73 | return $"PPPPlayer: (Rank): {rank} (CountryRank): {countryRank} (PP): {pp} (Country): {country}"; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /PPPredictor/Data/PPPScore.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.OpenAPIs; 2 | using PPPredictor.Utilities; 3 | using System; 4 | using static PPPredictor.Data.LeaderBoardDataTypes.AccSaberDataTypes; 5 | using static PPPredictor.Data.LeaderBoardDataTypes.BeatLeaderDataTypes; 6 | using static PPPredictor.Data.LeaderBoardDataTypes.HitBloqDataTypes; 7 | using static PPPredictor.Data.LeaderBoardDataTypes.ScoreSaberDataTypes; 8 | 9 | namespace PPPredictor.Data 10 | { 11 | class PPPScore 12 | { 13 | readonly DateTimeOffset timeSet; 14 | readonly double pp; 15 | readonly string songHash; 16 | readonly double difficulty; 17 | readonly string gameMode; 18 | 19 | public DateTimeOffset TimeSet { get => timeSet; } 20 | public double Pp { get => pp; } 21 | public string SongHash { get => songHash; } 22 | public double Difficulty1 { get => difficulty; } 23 | public string GameMode { get => gameMode; } 24 | 25 | public PPPScore(ScoreSaberPlayerScore playerScore) 26 | { 27 | timeSet = playerScore.score.timeSet; 28 | pp = playerScore.score.pp; 29 | songHash = playerScore.leaderboard.songHash; 30 | difficulty = playerScore.leaderboard.difficulty.difficulty; 31 | gameMode = playerScore.leaderboard.difficulty.gameMode; 32 | } 33 | 34 | public PPPScore(BeatLeaderPlayerScore playerScore) 35 | { 36 | if(long.TryParse(playerScore.timeset, out long timeSetLong)){ 37 | timeSet = new DateTime(1970, 1, 1).AddSeconds(timeSetLong); 38 | } 39 | pp = (int)playerScore.leaderboard.difficulty.status == (int)BeatLeaderDifficultyStatus.ranked ? playerScore.pp : 0; 40 | songHash = playerScore.leaderboard.song.hash; 41 | difficulty = playerScore.leaderboard.difficulty.value; 42 | gameMode = "Solo" + playerScore.leaderboard.difficulty.modeName; 43 | } 44 | 45 | public PPPScore(HitBloqScores playerScore) 46 | { 47 | timeSet = new DateTime(1970, 1, 1).AddSeconds(playerScore.time); 48 | pp = playerScore.cr_received; 49 | var (hash, diff, mode) = PPCalculatorHitBloq.ParseHashDiffAndMode(playerScore.song_id); 50 | songHash = hash; 51 | difficulty = ParsingUtil.ParseDifficultyNameToInt(diff); 52 | gameMode = mode; 53 | } 54 | 55 | public PPPScore(AccSaberScores playerScore) 56 | { 57 | timeSet = playerScore.timeSet; 58 | pp = playerScore.ap; 59 | songHash = playerScore.songHash?.ToUpper(); 60 | difficulty = ParsingUtil.ParseDifficultyNameToInt(playerScore.difficulty); 61 | gameMode = "SoloStandard"; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /PPPredictor/Data/PPPScoreCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using static PPPredictor.Data.LeaderBoardDataTypes.BeatLeaderDataTypes; 3 | using static PPPredictor.Data.LeaderBoardDataTypes.HitBloqDataTypes; 4 | using static PPPredictor.Data.LeaderBoardDataTypes.ScoreSaberDataTypes; 5 | using static PPPredictor.Data.LeaderBoardDataTypes.AccSaberDataTypes; 6 | 7 | namespace PPPredictor.Data 8 | { 9 | class PPPScoreCollection 10 | { 11 | private readonly List lsPPPScore = new List(); 12 | private readonly double page; 13 | private readonly double itemsPerPage; 14 | private readonly double total; 15 | 16 | public List LsPPPScore { get => lsPPPScore; } 17 | public double Page { get => page; } 18 | public double ItemsPerPage { get => itemsPerPage; } 19 | public double Total { get => total; } 20 | 21 | public PPPScoreCollection() 22 | { 23 | lsPPPScore = new List(); 24 | page = -1; 25 | itemsPerPage = -1; 26 | total = -1; 27 | } 28 | 29 | public PPPScoreCollection(ScoreSaberPlayerScoreList scoreSaberPlayerScoreList) 30 | { 31 | this.page = scoreSaberPlayerScoreList.metadata.page; 32 | this.itemsPerPage = scoreSaberPlayerScoreList.metadata.itemsPerPage; 33 | this.total = scoreSaberPlayerScoreList.metadata.total; 34 | foreach (var playerScore in scoreSaberPlayerScoreList.playerScores) 35 | { 36 | lsPPPScore.Add(new PPPScore(playerScore)); 37 | } 38 | } 39 | 40 | public PPPScoreCollection(BeatLeaderPlayerScoreList beatLeaderPlayerScoreList) 41 | { 42 | this.page = beatLeaderPlayerScoreList.metadata.page; 43 | this.itemsPerPage = beatLeaderPlayerScoreList.metadata.itemsPerPage; 44 | this.total = beatLeaderPlayerScoreList.metadata.total; 45 | foreach (var playerScore in beatLeaderPlayerScoreList.data) 46 | { 47 | lsPPPScore.Add(new PPPScore(playerScore)); 48 | } 49 | } 50 | 51 | public PPPScoreCollection(List lsHitBloqScores, int page) 52 | { 53 | this.page = page; 54 | this.itemsPerPage = 10; 55 | this.total = (lsHitBloqScores.Count > 0) ? page * itemsPerPage + 1 : 0; 56 | foreach (var playerScore in lsHitBloqScores) 57 | { 58 | lsPPPScore.Add(new PPPScore(playerScore)); 59 | } 60 | } 61 | 62 | public PPPScoreCollection(List lsHitBloqScores, int page) 63 | { 64 | this.page = page; 65 | this.itemsPerPage = 10; 66 | this.total = (lsHitBloqScores.Count > 0) ? page * itemsPerPage + 1 : 0; 67 | foreach (var playerScore in lsHitBloqScores) 68 | { 69 | lsPPPScore.Add(new PPPScore(playerScore)); 70 | } 71 | } 72 | 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /PPPredictor/Data/PPPStarRating.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | using Newtonsoft.Json; 4 | using PPPredictor.Utilities; 5 | using static PPPredictor.Data.LeaderBoardDataTypes.BeatLeaderDataTypes; 6 | 7 | namespace PPPredictor.Data 8 | { 9 | internal class PPPStarRating 10 | { 11 | private double _stars = 0; 12 | private double _multiplier = 1; 13 | private double _predictedAcc = 0; 14 | private double _passRating = 0; 15 | private double _accRating = 0; 16 | private double _techRating = 0; 17 | private bool? _rankedBeatLeader = null; 18 | private Dictionary _modifiersRating = new Dictionary(); 19 | private Dictionary _modifierValues = new Dictionary(); 20 | 21 | [DefaultValue(null)] 22 | public bool? RankedBeatLeader{ get => _rankedBeatLeader; set => _rankedBeatLeader = value; } 23 | [DefaultValue(0d)] 24 | public double Stars { get => _stars; set => _stars = value; } 25 | [DefaultValue(0d)] 26 | public double PredictedAcc { get => _predictedAcc; set => _predictedAcc = value; } 27 | [DefaultValue(0d)] 28 | public double PassRating { get => _passRating; set => _passRating = value; } 29 | [DefaultValue(0d)] 30 | public double AccRating { get => _accRating; set => _accRating = value; } 31 | [DefaultValue(0d)] 32 | public double TechRating { get => _techRating; set => _techRating = value; } 33 | [DefaultValue(null)] 34 | public Dictionary ModifiersRating { get => _modifiersRating; set => _modifiersRating = value; } 35 | [DefaultValue(null)] 36 | public Dictionary ModifierValues { get => _modifierValues; set => _modifierValues = value; } 37 | [JsonIgnoreAttribute] 38 | [DefaultValue(1)] 39 | public double Multiplier { get => _multiplier; set => _multiplier = value; } 40 | 41 | internal PPPStarRating() 42 | { 43 | } 44 | 45 | internal PPPStarRating(double starRating) 46 | { 47 | _stars = _predictedAcc = _passRating = _accRating = _techRating = starRating; 48 | } 49 | 50 | internal PPPStarRating(BeatLeaderDifficulty beatLeaderDifficulty) 51 | { 52 | _rankedBeatLeader = beatLeaderDifficulty.status == (int)BeatLeaderDifficultyStatus.ranked; 53 | _predictedAcc = beatLeaderDifficulty.predictedAcc.GetValueOrDefault(); 54 | _passRating = beatLeaderDifficulty.passRating.GetValueOrDefault(); 55 | _accRating = beatLeaderDifficulty.accRating.GetValueOrDefault(); 56 | _techRating = beatLeaderDifficulty.techRating.GetValueOrDefault(); 57 | _modifiersRating = beatLeaderDifficulty.modifiersRating ?? null; 58 | _modifierValues = beatLeaderDifficulty.modifierValues ?? null; 59 | } 60 | 61 | internal PPPStarRating(double mulitplier, double accRating, double passRating, double techRating, bool? rankedBeatLeader) 62 | { 63 | _rankedBeatLeader = rankedBeatLeader; 64 | _multiplier = mulitplier; 65 | _accRating = accRating; 66 | _passRating = passRating; 67 | _techRating = techRating; 68 | } 69 | 70 | internal bool IsRanked() 71 | { 72 | if(_rankedBeatLeader == false){ 73 | return false; 74 | } 75 | 76 | return _predictedAcc > 0 || _passRating > 0 || _accRating > 0 || _techRating > 0 || _stars > 0; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /PPPredictor/Data/PPPWebSocketData.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Interfaces; 2 | 3 | namespace PPPredictor.Data 4 | { 5 | internal class PPPWebSocketData 6 | { 7 | public string leaderboardName; 8 | public string userId; 9 | public int context; 10 | public string hash; 11 | } 12 | 13 | #region scoresaber 14 | class PPPWsScoreSaberCommand : IPPPRawWebsocketData 15 | { 16 | public PPPWsScoreSaberData commandData; 17 | 18 | public PPPWebSocketData ConvertToPPPWebSocketData(string leaderboardName) 19 | { 20 | var data = new PPPWebSocketData(); 21 | data.leaderboardName = leaderboardName; 22 | data.userId = commandData.score.leaderboardPlayerInfo.id; 23 | data.hash = $"{commandData.leaderboard.songHash}_{commandData.leaderboard.difficulty.gameMode}_{commandData.leaderboard.difficulty.difficulty}".ToUpper(); 24 | return data; 25 | } 26 | } 27 | 28 | class PPPWsScoreSaberData 29 | { 30 | public PPPWsScoreSaberScore score; 31 | public PPPWsScoreSaberLeaderBoard leaderboard; 32 | } 33 | 34 | class PPPWsScoreSaberLeaderBoard 35 | { 36 | public string songHash; 37 | public PPPWsScoreSaberDifficulty difficulty; 38 | } 39 | 40 | class PPPWsScoreSaberDifficulty 41 | { 42 | public int difficulty; 43 | public string gameMode; 44 | public string difficultyRaw; 45 | } 46 | 47 | class PPPWsScoreSaberScore 48 | { 49 | public double pp; 50 | public WebSocketScoreCommandPlayerInfo leaderboardPlayerInfo; 51 | } 52 | 53 | class WebSocketScoreCommandPlayerInfo 54 | { 55 | public string id; 56 | } 57 | #endregion 58 | 59 | #region beatleader 60 | class PPPWsBeatLeaderData : IPPPRawWebsocketData 61 | { 62 | public int validContexts { get; set; } 63 | public string playerId { get; set; } 64 | public double pp { get; set; } 65 | public PPPWsBeatLeaderPlayer player { get; set; } 66 | 67 | public PPPWsBeatLeaderLeaderboard leaderboard { get; set; } 68 | 69 | public PPPWebSocketData ConvertToPPPWebSocketData(string leaderboardName) 70 | { 71 | var data = new PPPWebSocketData(); 72 | data.leaderboardName = leaderboardName; 73 | data.context = validContexts; 74 | data.userId = playerId; 75 | data.hash = $"{leaderboard.song.hash}_SOLO{leaderboard.difficulty.modeName}_{leaderboard.difficulty.value}".ToUpper(); 76 | return data; 77 | } 78 | } 79 | 80 | class PPPWsBeatLeaderPlayer 81 | { 82 | public string id { get; set; } 83 | public string name { get; set; } 84 | } 85 | 86 | class PPPWsBeatLeaderLeaderboard 87 | { 88 | public PPPWsBeatLeaderSong song { get; set; } 89 | 90 | 91 | public PPPWsBeatLeaderDifficulty difficulty; 92 | } 93 | 94 | class PPPWsBeatLeaderSong 95 | { 96 | public string hash { get; set; } 97 | public string name { get; set; } 98 | } 99 | 100 | class PPPWsBeatLeaderDifficulty 101 | { 102 | public int value { get; set; } 103 | public int mode { get; set; } 104 | public string difficultyName { get; set; } 105 | 106 | public string modeName { get; set; } 107 | } 108 | #endregion 109 | } 110 | -------------------------------------------------------------------------------- /PPPredictor/Data/RankGainResult.cs: -------------------------------------------------------------------------------- 1 | namespace PPPredictor.Data 2 | { 3 | class RankGainResult 4 | { 5 | private double _rankGlobal; 6 | private double _rankCountry; 7 | private double _rankGainGlobal; 8 | private double _rankGainCountry; 9 | private bool _isRankGainCanceledByLimit; 10 | 11 | public double RankGlobal { get => _rankGlobal; } 12 | public double RankCountry { get => _rankCountry; } 13 | public double RankGainGlobal { get => _rankGainGlobal; } 14 | public double RankGainCountry { get => _rankGainCountry; } 15 | public bool IsRankGainCanceledByLimit { get => _isRankGainCanceledByLimit; set => _isRankGainCanceledByLimit = value; } 16 | 17 | public RankGainResult() 18 | { 19 | _rankGlobal = -1; 20 | _rankCountry = -1; 21 | _rankGainGlobal = -1; 22 | _rankGainCountry = -1; 23 | _isRankGainCanceledByLimit = false; 24 | } 25 | 26 | public RankGainResult(double rankGlobal, double rankCountry, PPPPlayer currentPlayer, bool isRankGainCanceledByLimit = false) 27 | { 28 | _rankCountry = rankCountry; 29 | _rankGlobal = rankGlobal; 30 | _rankGainGlobal = currentPlayer.Rank - rankGlobal; 31 | _rankGainCountry = currentPlayer.CountryRank - rankCountry; 32 | _isRankGainCanceledByLimit = isRankGainCanceledByLimit; 33 | } 34 | 35 | public RankGainResult(double rankGlobal, double rankCountry, double rankGainGlobal, double rankGainCountry) 36 | { 37 | _rankGlobal = rankGlobal; 38 | _rankCountry = rankCountry; 39 | _rankGainGlobal = rankGainGlobal; 40 | _rankGainCountry = rankGainCountry; 41 | _isRankGainCanceledByLimit = false; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /PPPredictor/Data/SVector3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace PPPredictor.Data 5 | { 6 | //http://answers.unity.com/answers/1580674/view.html 7 | [Serializable] 8 | struct SVector3 9 | { 10 | public float x; 11 | public float y; 12 | public float z; 13 | 14 | public SVector3(float x, float y, float z) 15 | { 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | } 20 | 21 | public override string ToString() 22 | => $"[x, y, z]"; 23 | 24 | public static implicit operator Vector3(SVector3 s) 25 | => new Vector3(s.x, s.y, s.z); 26 | 27 | public static implicit operator SVector3(Vector3 v) 28 | => new SVector3(v.x, v.y, v.z); 29 | 30 | 31 | public static SVector3 operator +(SVector3 a, SVector3 b) 32 | => new SVector3(a.x + b.x, a.y + b.y, a.z + b.z); 33 | 34 | public static SVector3 operator -(SVector3 a, SVector3 b) 35 | => new SVector3(a.x - b.x, a.y - b.y, a.z - b.z); 36 | 37 | public static SVector3 operator -(SVector3 a) 38 | => new SVector3(-a.x, -a.y, -a.z); 39 | 40 | public static SVector3 operator *(SVector3 a, float m) 41 | => new SVector3(a.x * m, a.y * m, a.z * m); 42 | 43 | public static SVector3 operator *(float m, SVector3 a) 44 | => new SVector3(a.x * m, a.y * m, a.z * m); 45 | 46 | public static SVector3 operator /(SVector3 a, float d) 47 | => new SVector3(a.x / d, a.y / d, a.z / d); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PPPredictor/Data/ShortScore.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace PPPredictor.Data 5 | { 6 | class ShortScore 7 | { 8 | private readonly string _searchstring; 9 | private double _pp; 10 | private PPPStarRating _starRating; 11 | private DateTime _fetchTime; 12 | 13 | public string Searchstring { get => _searchstring; } 14 | public double Pp { get => _pp; set => _pp = value; } 15 | public PPPStarRating StarRating { get => _starRating; set => _starRating = value; } 16 | public DateTime FetchTime { get => _fetchTime; set => _fetchTime = value; } 17 | 18 | public ShortScore(string searchstring, double pp) 19 | { 20 | this._searchstring = searchstring.ToUpper(); 21 | this._pp = pp; 22 | this._starRating = new PPPStarRating(); 23 | } 24 | 25 | public ShortScore(string searchstring, PPPStarRating starRating, DateTime fetchTime) 26 | { 27 | this._searchstring = searchstring.ToUpper(); 28 | this._starRating = starRating; 29 | this._fetchTime = fetchTime; 30 | } 31 | 32 | [JsonConstructor] 33 | public ShortScore(string searchstring, double pp, PPPStarRating starRating, DateTime fetchTime) 34 | { 35 | this._searchstring = searchstring.ToUpper(); 36 | this._pp = pp; 37 | this._fetchTime = fetchTime; 38 | this._starRating = starRating ?? new PPPStarRating(); 39 | } 40 | 41 | public bool ShouldSerializeStarRating() 42 | { 43 | return _starRating.IsRanked(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PPPredictor/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | True 6 | BSIPA 7 | 8 | -------------------------------------------------------------------------------- /PPPredictor/Installers/CoreInstaller.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.OpenAPIs; 2 | using PPPredictor.Utilities; 3 | using Zenject; 4 | 5 | namespace PPPredictor.Installers 6 | { 7 | class CoreInstaller : Installer 8 | { 9 | public override void InstallBindings() 10 | { 11 | Container.BindInterfacesAndSelfTo>().AsSingle(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PPPredictor/Installers/GamePlayInstaller.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Utilities; 2 | using Zenject; 3 | 4 | namespace PPPredictor.Installers 5 | { 6 | internal class GamePlayInstaller : Installer 7 | { 8 | public override void InstallBindings() 9 | { 10 | Container.BindInterfacesAndSelfTo().AsSingle(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PPPredictor/Installers/MainMenuInstaller.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Utilities; 2 | using Zenject; 3 | 4 | namespace PPPredictor.Installers 5 | { 6 | internal class MainMenuInstaller : Installer 7 | { 8 | public override void InstallBindings() 9 | { 10 | Container.BindInterfacesAndSelfTo().AsSingle(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PPPredictor/Installers/PPPPredictorDisplayInstaller.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.UI.ViewController; 2 | using Zenject; 3 | 4 | namespace PPPredictor 5 | { 6 | internal class PPPPredictorDisplayInstaller : Installer 7 | { 8 | public override void InstallBindings() 9 | { 10 | Container.BindInterfacesTo().AsSingle(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PPPredictor/Interfaces/IAccSaberAPI.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using static PPPredictor.Data.LeaderBoardDataTypes.AccSaberDataTypes; 4 | 5 | namespace PPPredictor.Interfaces 6 | { 7 | internal interface IAccSaberAPI 8 | { 9 | Task> GetAllRankedMaps(); 10 | Task> GetRankedMaps(string mapPool); 11 | Task> GetAccSaberMapPools(); 12 | Task GetAccSaberUserByPool(long userId, string poolIdent); 13 | Task> GetPlayerListForMapPool(double page, string mapPoolId); 14 | Task> GetAllScores(string userId); 15 | Task> GetAllScoresByPool(string userId, string poolId); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PPPredictor/Interfaces/IBeatLeaderAPI.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using static PPPredictor.Data.LeaderBoardDataTypes.BeatLeaderDataTypes; 3 | 4 | namespace PPPredictor.Interfaces 5 | { 6 | internal interface IBeatLeaderAPI 7 | { 8 | Task GetEvents(); 9 | 10 | Task GetSongByHash(string hash); 11 | 12 | Task GetPlayer(long userId, long leaderboardContextId); 13 | 14 | Task GetPlayerScores(string userId, string sortBy, string order, int page, int count, long leaderboardContextId, long? eventId = null); 15 | 16 | Task GetPlayersInLeaderboard(string sortBy, int page, int? count, string order, long leaderboardContextId); 17 | 18 | Task GetPlayersInEventLeaderboard(long eventId, string sortBy, int page, int? count, string order); 19 | 20 | Task GetPlayList(long playListId); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PPPredictor/Interfaces/IHitBloqAPI.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using static PPPredictor.Data.LeaderBoardDataTypes.HitBloqDataTypes; 4 | 5 | namespace PPPredictor.Interfaces 6 | { 7 | internal interface IHitBloqAPI 8 | { 9 | Task GetHitBloqUserIdByUserId(string id); 10 | 11 | Task> GetHitBloqMapPools(); 12 | 13 | Task GetHitBloqMapPoolDetails(string poolIdent, int page); 14 | 15 | Task GetHitBloqUserByPool(long userId, string poolIdent); 16 | 17 | Task> GetRecentScores(string userId, string poolId, int page); 18 | 19 | Task> GetAllScores(string userId, string poolId); 20 | 21 | Task GetPlayerListForMapPool(double page, string mapPoolId); 22 | 23 | Task GetPlayerRankByCr(string mapPoolId, double cr); 24 | 25 | Task GetLeaderBoardInfo(string searchString); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /PPPredictor/Interfaces/IPPPCurve.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Data; 2 | using PPPredictor.Data.Curve; 3 | using PPPredictor.Utilities; 4 | 5 | namespace PPPredictor.Interfaces 6 | { 7 | interface IPPPCurve 8 | { 9 | bool IsDummy { get; } 10 | double CalculatePPatPercentage(PPPBeatMapInfo _currentBeatMapInfo, double percentage, bool failed, bool paused, LeaderboardContext leaderboardContext = LeaderboardContext.None); 11 | double CalculateMaxPP(PPPBeatMapInfo _currentBeatMapInfo, LeaderboardContext leaderboardContext = LeaderboardContext.None); 12 | CurveInfo ToCurveInfo(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PPPredictor/Interfaces/IPPPRawWebsocketData.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Data; 2 | 3 | namespace PPPredictor.Interfaces 4 | { 5 | internal interface IPPPRawWebsocketData 6 | { 7 | PPPWebSocketData ConvertToPPPWebSocketData(string leaderboardName); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /PPPredictor/Interfaces/IPPPWebSocket.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Data; 2 | using System; 3 | 4 | namespace PPPredictor.Interfaces 5 | { 6 | internal interface IPPPWebSocket 7 | { 8 | event EventHandler OnScoreSet; 9 | void StopWebSocket(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PPPredictor/Interfaces/IPPPredictor.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Data; 2 | using PPPredictor.Data.DisplayInfos; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | namespace PPPredictor.Interfaces 8 | { 9 | interface IPPPredictor 10 | { 11 | void ChangeGameplayModifiers(GameplaySetupViewController gameplaySetupViewController); 12 | void DetailContentChanged(LevelSelectionNavigationController lvlSelectionNavigationCtrl, StandardLevelDetailViewController.ContentType contentType); 13 | void DifficultyChanged(LevelSelectionNavigationController lvlSelectionNavigationCtrl, IDifficultyBeatmap beatmap); 14 | Task UpdateCurrentAndCheckResetSession(bool doResetSession); 15 | void ScoreSet(PPPWebSocketData data); 16 | void RefreshCurrentData(int fetchLength, bool refreshStars = false, bool fetchOnePage = false); 17 | void ResetDisplay(bool resetAll); 18 | double CalculatePPatPercentage(double percentage, PPPBeatMapInfo beatMapInfo, bool levelFailed = false, bool levelPaused = false); 19 | double CalculateMaxPP(); 20 | PPPBeatMapInfo GetModifiedBeatMapInfo(GameplayModifiers gameplayModifiers, bool levelFailed = false, bool levelPaused = false); 21 | double CalculatePPGain(double pp); 22 | bool IsRanked(); 23 | 24 | double? GetPersonalBest(); 25 | void CalculatePP(); 26 | void SetActive(bool setActive); 27 | PPPMapPool FindPoolWithSyncURL(string syncUrl); 28 | Task GetMapPoolIconData(); 29 | Task UpdateCurrentBeatMapInfos(CustomBeatmapLevel selectedBeatmapLevel, IDifficultyBeatmap beatmap); 30 | event EventHandler OnDataLoading; 31 | event EventHandler OnDisplaySessionInfo; 32 | event EventHandler OnDisplayPPInfo; 33 | event EventHandler OnMapPoolRefreshed; 34 | float Percentage { get; set; } 35 | string LeaderBoardName { get; } 36 | string LeaderBoardIcon { get; } 37 | string MapPoolIcon { get; } 38 | byte[] MapPoolIconData { get; set; } 39 | List MapPoolOptions { get; } 40 | object CurrentMapPool { get; set; } 41 | string PPSuffix { get; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PPPredictor/Interfaces/IPPPredictorMgr.cs: -------------------------------------------------------------------------------- 1 | using BeatSaberPlaylistsLib.Types; 2 | using PPPredictor.Data.DisplayInfos; 3 | using PPPredictor.Data; 4 | using PPPredictor.Utilities; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Threading.Tasks; 8 | 9 | namespace PPPredictor.Interfaces 10 | { 11 | interface IPPPredictorMgr 12 | { 13 | bool IsLeftArrowActive { get; } 14 | bool IsRightArrowActive { get; } 15 | bool IsMapPoolDropDownActive { get; } 16 | bool IsLeaderboardNavigationActive { get; } 17 | IPPPredictor CurrentPPPredictor { get; } 18 | 19 | WebSocketMgr WebsocketMgr { get; } 20 | 21 | event EventHandler ViewActivated; 22 | event EventHandler OnDataLoading; 23 | event EventHandler OnDisplaySessionInfo; 24 | event EventHandler OnDisplayPPInfo; 25 | event EventHandler OnMapPoolRefreshed; 26 | 27 | void ResetPredictors(); 28 | 29 | void CyclePredictors(int offset); 30 | 31 | void RestartOverlayServer(); 32 | 33 | void ChangeGameplayModifiers(GameplaySetupViewController gameplaySetupViewController); 34 | 35 | void DetailContentChanged(LevelSelectionNavigationController lvlSelectionNavigationCtrl, StandardLevelDetailViewController.ContentType contentType); 36 | 37 | void DifficultyChanged(LevelSelectionNavigationController lvlSelectionNavigationCtrl, IDifficultyBeatmap beatmap); 38 | 39 | void UpdateCurrentAndCheckResetSession(bool v); 40 | 41 | void ScoreSet(string leaderboardName, PPPWebSocketData data); 42 | 43 | void RefreshCurrentData(int v, bool refreshStars = false); 44 | 45 | void SetPercentage(float percentage); 46 | 47 | double GetPercentage(); 48 | 49 | void ResetDisplay(bool resetAll); 50 | 51 | double GetPPAtPercentageForCalculator(Leaderboard leaderBoardName, double percentage, bool levelFailed, bool levelPaused, PPPBeatMapInfo beatMapInfo); 52 | 53 | double? GetPersonalBest(Leaderboard leaderBoardName); 54 | 55 | string GetPPSuffixForLeaderboard(Leaderboard leaderBoardName); 56 | 57 | double GetMaxPPForCalculator(Leaderboard leaderBoardName); 58 | 59 | PPPBeatMapInfo GetModifiedBeatMapInfo(Leaderboard leaderBoardName, GameplayModifiers gameplayModifiers); 60 | 61 | bool IsRanked(Leaderboard leaderBoardName); 62 | 63 | double GetPPGainForCalculator(Leaderboard leaderBoardName, double pp); 64 | 65 | string GetLeaderboardIcon(Leaderboard leaderBoardName); 66 | string GetMapPoolIcon(Leaderboard leaderBoardName); 67 | 68 | Task GetLeaderboardIconData(Leaderboard leaderBoardName); 69 | 70 | void ActivateView(bool activate); 71 | 72 | List GetMapPoolsFromLeaderboard(Leaderboard leaderBoardName); 73 | 74 | Task UpdateCurrentBeatMapInfos(CustomBeatmapLevel selectedBeatmapLevel, IDifficultyBeatmap beatmap); 75 | 76 | void FindPoolWithSyncURL(IPlaylist playlist); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /PPPredictor/Interfaces/IScoresaberAPI.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using static PPPredictor.Data.LeaderBoardDataTypes.ScoreSaberDataTypes; 3 | 4 | namespace PPPredictor.Interfaces 5 | { 6 | internal interface IScoresaberAPI 7 | { 8 | Task GetPlayers(double? page); 9 | 10 | Task GetPlayer(long playerId); 11 | 12 | Task GetPlayerScores(string playerId, int? limit, int? page); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PPPredictor/OpenAPIs/PPPWebSocket.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using PPPredictor.Data; 3 | using PPPredictor.Interfaces; 4 | using PPPredictor.Utilities; 5 | using System; 6 | using System.Threading.Tasks; 7 | using WebSocketSharp; 8 | 9 | namespace PPPredictor.OpenAPIs 10 | { 11 | internal class PPPWebSocket : IPPPWebSocket where T : IPPPRawWebsocketData 12 | { 13 | private WebSocketSharp.WebSocket webSocket; 14 | public event EventHandler OnScoreSet; 15 | private string userId = string.Empty; 16 | private string _leaderboardName = string.Empty; 17 | private string _url = string.Empty; 18 | 19 | public PPPWebSocket(string url, string leaderboardName) 20 | { 21 | _ = StartWebSocket(url, leaderboardName); 22 | } 23 | 24 | private async Task StartWebSocket(string url, string leaderboardName) 25 | { 26 | this._leaderboardName = leaderboardName; 27 | this._url = url; 28 | try 29 | { 30 | userId = (await Plugin.GetUserInfoBS()).platformUserId; 31 | webSocket = new WebSocketSharp.WebSocket(url); 32 | webSocket.SslConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12; 33 | webSocket.OnMessage += WebSocket_OnMessage; 34 | webSocket.OnError += WebSocket_OnError; 35 | webSocket.Connect(); 36 | } 37 | catch (Exception ex) 38 | { 39 | Plugin.ErrorPrint($"Error creating Websocket for {leaderboardName}: {ex.Message}"); 40 | } 41 | } 42 | 43 | private void WebSocket_OnMessage(object sender, MessageEventArgs e) 44 | { 45 | try 46 | { 47 | var socketData = JsonConvert.DeserializeObject(e.Data); 48 | var v = socketData.ConvertToPPPWebSocketData(_leaderboardName); 49 | if (v.userId == userId) 50 | { 51 | OnScoreSet?.Invoke(this, v); 52 | } 53 | } 54 | catch (Exception ex) 55 | { 56 | if (e != null && e.Data != null && e.Data == "Connected to the ScoreSaber WSS") return; //Ignore SS connect message 57 | Plugin.ErrorPrint($"Error in Websocket for {_leaderboardName} OnMessage: {ex.Message}"); 58 | } 59 | } 60 | 61 | private async void WebSocket_OnError(object sender, ErrorEventArgs e) 62 | { 63 | Plugin.ErrorPrint($"Error in Websocket for {_leaderboardName} Retry connecting..."); 64 | await Task.Delay(5000); 65 | _ = StartWebSocket(_url, _leaderboardName); 66 | } 67 | 68 | public void StopWebSocket() 69 | { 70 | webSocket.OnMessage -= WebSocket_OnMessage; 71 | webSocket.OnError -= WebSocket_OnError; 72 | if (_leaderboardName != Leaderboard.BeatLeader.ToString()) 73 | { 74 | webSocket?.Close(); //Stop beatleader error when tying to disconnect... 75 | } 76 | webSocket = null; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /PPPredictor/OpenAPIs/accsaberapi.cs: -------------------------------------------------------------------------------- 1 | using PPPredictor.Interfaces; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http.Headers; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | using System.Diagnostics; 9 | using static PPPredictor.Data.LeaderBoardDataTypes.AccSaberDataTypes; 10 | 11 | namespace PPPredictor.OpenAPIs 12 | { 13 | internal class AccSaberApi : IAccSaberAPI 14 | { 15 | private static readonly string baseUrl = "http://api.accsaber.com"; 16 | private readonly HttpClient client; 17 | 18 | public AccSaberApi() 19 | { 20 | client = new HttpClient(); 21 | client.DefaultRequestHeaders.Accept.Clear(); 22 | client.DefaultRequestHeaders.Add("User-Agent", "PPPredictor"); 23 | client.DefaultRequestHeaders.Accept.Add( 24 | new MediaTypeWithQualityHeaderValue("application/json")); 25 | client.BaseAddress = new Uri(baseUrl); 26 | } 27 | 28 | [Conditional("ACCSABERNETWORK")] 29 | public void DebugPrintAccSaberNetwork(string message) 30 | { 31 | Plugin.DebugNetworkPrint($"AccSaberNetwork: {message}"); 32 | } 33 | 34 | public async Task> GetAllScores(string userId) 35 | { 36 | try 37 | { 38 | HttpResponseMessage response = await client.GetAsync($"/players/{userId}/scores?pageSize=9999"); 39 | DebugPrintAccSaberNetwork(response.RequestMessage.RequestUri.ToString()); 40 | if (response.IsSuccessStatusCode) 41 | { 42 | string result = await response.Content.ReadAsStringAsync(); 43 | return JsonConvert.DeserializeObject>(result); 44 | } 45 | } 46 | catch (Exception ex) 47 | { 48 | Plugin.ErrorPrint($"Error in GetAllScores: {ex.Message}"); 49 | } 50 | return new List(); 51 | } 52 | 53 | public async Task> GetAllScoresByPool(string userId, string poolId) 54 | { 55 | try 56 | { 57 | HttpResponseMessage response = await client.GetAsync($"/players/{userId}/{poolId}/scores"); 58 | DebugPrintAccSaberNetwork(response.RequestMessage.RequestUri.ToString()); 59 | if (response.IsSuccessStatusCode) 60 | { 61 | string result = await response.Content.ReadAsStringAsync(); 62 | return JsonConvert.DeserializeObject>(result); 63 | } 64 | } 65 | catch (Exception ex) 66 | { 67 | Plugin.ErrorPrint($"Error in GetAllScores: {ex.Message}"); 68 | } 69 | return new List(); 70 | } 71 | 72 | public async Task GetAccSaberUserByPool(long userId, string poolIdent) 73 | { 74 | try 75 | { 76 | HttpResponseMessage response = await client.GetAsync($"players/{userId}/{poolIdent}"); 77 | DebugPrintAccSaberNetwork(response.RequestMessage.RequestUri.ToString()); 78 | if (response.IsSuccessStatusCode) 79 | { 80 | string result = await response.Content.ReadAsStringAsync(); 81 | return JsonConvert.DeserializeObject(result); 82 | } 83 | } 84 | catch (Exception ex) 85 | { 86 | Plugin.ErrorPrint($"Error in GetHitBloqUserIdByUserId: {ex.Message}"); 87 | } 88 | return new AccSaberPlayer(); 89 | } 90 | 91 | public async Task> GetAccSaberMapPools() 92 | { 93 | try 94 | { 95 | HttpResponseMessage response = await client.GetAsync($"categories"); 96 | DebugPrintAccSaberNetwork(response.RequestMessage.RequestUri.ToString()); 97 | if (response.IsSuccessStatusCode) 98 | { 99 | string result = await response.Content.ReadAsStringAsync(); 100 | return JsonConvert.DeserializeObject>(result); 101 | } 102 | } 103 | catch (Exception ex) 104 | { 105 | Plugin.ErrorPrint($"Error in GetAccSaberMapPools: {ex.Message}"); 106 | } 107 | return new List(); 108 | } 109 | 110 | public async Task> GetAllRankedMaps() 111 | { 112 | try 113 | { 114 | HttpResponseMessage response = await client.GetAsync($"ranked-maps"); 115 | DebugPrintAccSaberNetwork(response.RequestMessage.RequestUri.ToString()); 116 | if (response.IsSuccessStatusCode) 117 | { 118 | string result = await response.Content.ReadAsStringAsync(); 119 | return JsonConvert.DeserializeObject>(result); 120 | } 121 | } 122 | catch (Exception ex) 123 | { 124 | Plugin.ErrorPrint($"Error in GetAllRankedMaps: {ex.Message}"); 125 | } 126 | return new List(); 127 | } 128 | 129 | public async Task> GetRankedMaps(string mapPool) 130 | { 131 | try 132 | { 133 | HttpResponseMessage response = await client.GetAsync($"ranked-maps/category/{mapPool}"); 134 | DebugPrintAccSaberNetwork(response.RequestMessage.RequestUri.ToString()); 135 | if (response.IsSuccessStatusCode) 136 | { 137 | string result = await response.Content.ReadAsStringAsync(); 138 | return JsonConvert.DeserializeObject>(result); 139 | } 140 | } 141 | catch (Exception ex) 142 | { 143 | Plugin.ErrorPrint($"Error in GetRankedMaps: {ex.Message}"); 144 | } 145 | return new List(); 146 | } 147 | 148 | public async Task> GetPlayerListForMapPool(double page, string mapPoolId) 149 | { 150 | try 151 | { 152 | HttpResponseMessage response = await client.GetAsync($"categories/{mapPoolId}/standings"); 153 | DebugPrintAccSaberNetwork(response.RequestMessage.RequestUri.ToString()); 154 | if (response.IsSuccessStatusCode) 155 | { 156 | string result = await response.Content.ReadAsStringAsync(); 157 | return JsonConvert.DeserializeObject>(result); 158 | } 159 | } 160 | catch (Exception ex) 161 | { 162 | Plugin.ErrorPrint($"Error in GetPlayerListForMapPool: {ex.Message}"); 163 | } 164 | return new List(); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /PPPredictor/OpenAPIs/scoresaberapi.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using PPPredictor.Interfaces; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Net.Http; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | using static PPPredictor.Data.LeaderBoardDataTypes.ScoreSaberDataTypes; 9 | 10 | namespace PPPredictor.OpenAPIs 11 | { 12 | internal class ScoresaberAPI : IScoresaberAPI 13 | { 14 | private static readonly string baseUrl = "https://scoresaber.com/api/"; 15 | private readonly HttpClient client; 16 | 17 | public ScoresaberAPI() 18 | { 19 | client = new HttpClient(); 20 | client.DefaultRequestHeaders.Accept.Clear(); 21 | client.DefaultRequestHeaders.Add("User-Agent", "PPPredictor"); 22 | client.DefaultRequestHeaders.Accept.Add( 23 | new MediaTypeWithQualityHeaderValue("application/json")); 24 | client.BaseAddress = new Uri(baseUrl); 25 | } 26 | 27 | [Conditional("SCORESABERNETWORK")] 28 | public void DebugPrintBeatLeaderNetwork(string message) 29 | { 30 | Plugin.DebugNetworkPrint($"ScoreSaberNetwork: {message}"); 31 | } 32 | 33 | public async Task GetPlayers(double? page) 34 | { 35 | try 36 | { 37 | HttpResponseMessage response = await client.GetAsync($"players?&page={page}&withMetadata=true"); 38 | DebugPrintBeatLeaderNetwork(response.RequestMessage.RequestUri.ToString()); 39 | if (response.IsSuccessStatusCode) 40 | { 41 | string result = await response.Content.ReadAsStringAsync(); 42 | return JsonConvert.DeserializeObject(result); 43 | } 44 | } 45 | catch (Exception ex) 46 | { 47 | Plugin.ErrorPrint($"Error in scoresaberapi GetPlayers: {ex.Message}"); 48 | } 49 | return new ScoreSaberPlayerList(); 50 | } 51 | 52 | public async Task GetPlayer(long playerId) 53 | { 54 | try 55 | { 56 | HttpResponseMessage response = await client.GetAsync($"player/{playerId}/basic"); 57 | DebugPrintBeatLeaderNetwork(response.RequestMessage.RequestUri.ToString()); 58 | if (response.IsSuccessStatusCode) 59 | { 60 | string result = await response.Content.ReadAsStringAsync(); 61 | return JsonConvert.DeserializeObject(result); 62 | } 63 | } 64 | catch (Exception ex) 65 | { 66 | Plugin.ErrorPrint($"Error in scoresaberapi GetPlayer: {ex.Message}"); 67 | } 68 | return new ScoreSaberPlayer(); 69 | } 70 | 71 | public async Task GetPlayerScores(string playerId, int? limit, int? page) 72 | { 73 | try 74 | { 75 | HttpResponseMessage response = await client.GetAsync($"player/{playerId}/scores?limit={limit}&sort=recent&page={page}&withMetadata=true"); 76 | DebugPrintBeatLeaderNetwork(response.RequestMessage.RequestUri.ToString()); 77 | if (response.IsSuccessStatusCode) 78 | { 79 | string result = await response.Content.ReadAsStringAsync(); 80 | return JsonConvert.DeserializeObject(result); 81 | } 82 | } 83 | catch (Exception ex) 84 | { 85 | Plugin.ErrorPrint($"Error in scoresaberapi GetPlayerScores: {ex.Message}"); 86 | } 87 | return new ScoreSaberPlayerScoreList(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /PPPredictor/OverlayServer/WebSocketOverlayServer.cs: -------------------------------------------------------------------------------- 1 | using WebSocketSharp; 2 | using WebSocketSharp.Server; 3 | 4 | namespace PPPredictor.OverlayServer 5 | { 6 | internal class WebSocketOverlayServer 7 | { 8 | private WebSocketServer server; 9 | 10 | public void StartSocket() 11 | { 12 | server = new WebSocketServer($"ws://localhost:{Plugin.ProfileInfo.StreamOverlayPort}"); 13 | server.AddWebSocketService("/socket"); 14 | server.Start(); 15 | } 16 | 17 | public void CloseSocket() 18 | { 19 | server.Stop(); 20 | } 21 | 22 | public void SendData(string s) 23 | { 24 | if (server != null) 25 | { 26 | server.WebSocketServices["/socket"].Sessions.Broadcast(s); 27 | } 28 | } 29 | } 30 | 31 | internal class PPPreditorWS : WebSocketBehavior 32 | { 33 | protected override void OnMessage(MessageEventArgs e) 34 | { 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /PPPredictor/Plugin.cs: -------------------------------------------------------------------------------- 1 | using IPA; 2 | using PPPredictor.Data; 3 | using PPPredictor.Installers; 4 | using PPPredictor.UI.ViewController; 5 | using PPPredictor.Utilities; 6 | using SiraUtil.Zenject; 7 | using System.Threading.Tasks; 8 | using IPALogger = IPA.Logging.Logger; 9 | 10 | namespace PPPredictor 11 | { 12 | [Plugin(RuntimeOptions.DynamicInit)] 13 | class Plugin 14 | { 15 | internal static string Beta = string.Empty; 16 | internal static Plugin Instance { get; private set; } 17 | internal static IPALogger Log { get; private set; } 18 | 19 | internal static ProfileInfo ProfileInfo; 20 | 21 | internal static PPPredictorViewController pppViewController; 22 | 23 | //Only Used for UnitTests 24 | internal Plugin() 25 | { 26 | Instance = this; 27 | ProfileInfo = new ProfileInfo(); 28 | } 29 | 30 | [Init] 31 | /// 32 | /// Called when the plugin is first loaded by IPA (either when the game starts or when the plugin is enabled if it starts disabled). 33 | /// [Init] methods that use a Constructor or called before regular methods like InitWithConfig. 34 | /// Only use [Init] with one Constructor. 35 | /// 36 | public Plugin(IPALogger logger, Zenjector zenjector) 37 | { 38 | Instance = this; 39 | Log = logger; 40 | ProfileInfo = ProfileInfoMgr.LoadProfileInfo(); 41 | zenjector.UseSiraSync(); 42 | zenjector.Install(Location.Menu); 43 | zenjector.Install(Location.Menu); 44 | zenjector.Install(Location.App); 45 | zenjector.Install(Location.StandardPlayer | Location.CampaignPlayer); 46 | } 47 | 48 | [OnStart] 49 | public void OnApplicationStart() 50 | { 51 | } 52 | 53 | [OnExit] 54 | public void OnApplicationQuit() 55 | { 56 | ProfileInfoMgr.SaveProfile(ProfileInfo); 57 | } 58 | 59 | public static void ErrorPrint(string text) 60 | { 61 | Plugin.Log?.Error(text); 62 | } 63 | 64 | public static void DebugPrint(string text) 65 | { 66 | Plugin.Log?.Error(text); 67 | } 68 | 69 | public static void DebugNetworkPrint(string text) 70 | { 71 | Plugin.Log?.Error(text); 72 | } 73 | 74 | internal static async Task GetUserInfoBS() 75 | { 76 | return await BS_Utils.Gameplay.GetUserInfo.GetUserAsync(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /PPPredictor/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: InternalsVisibleTo("UnitTests")] 9 | [assembly: AssemblyTitle("PPPredictor")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("PPPredictor")] 14 | [assembly: AssemblyCopyright("Copyright © 2024")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [assembly: Guid("8d3c1778-c03d-44cd-a00e-4295f182a607")] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: AssemblyVersion("1.1.0")] 37 | [assembly: AssemblyFileVersion("1.1.0")] 38 | -------------------------------------------------------------------------------- /PPPredictor/Resources/LeaderBoardLogos/AccSaber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/no-1-noob/PPPredictor/c4baa17737ebd21b92a70ab2c9a4a51c0003ba92/PPPredictor/Resources/LeaderBoardLogos/AccSaber.png -------------------------------------------------------------------------------- /PPPredictor/Resources/LeaderBoardLogos/BeatLeader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/no-1-noob/PPPredictor/c4baa17737ebd21b92a70ab2c9a4a51c0003ba92/PPPredictor/Resources/LeaderBoardLogos/BeatLeader.png -------------------------------------------------------------------------------- /PPPredictor/Resources/LeaderBoardLogos/HitBloq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/no-1-noob/PPPredictor/c4baa17737ebd21b92a70ab2c9a4a51c0003ba92/PPPredictor/Resources/LeaderBoardLogos/HitBloq.png -------------------------------------------------------------------------------- /PPPredictor/Resources/LeaderBoardLogos/ScoreSaber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/no-1-noob/PPPredictor/c4baa17737ebd21b92a70ab2c9a4a51c0003ba92/PPPredictor/Resources/LeaderBoardLogos/ScoreSaber.png -------------------------------------------------------------------------------- /PPPredictor/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/no-1-noob/PPPredictor/c4baa17737ebd21b92a70ab2c9a4a51c0003ba92/PPPredictor/Resources/icon.png -------------------------------------------------------------------------------- /PPPredictor/UI/PPPredictorFlowCoordinator.cs: -------------------------------------------------------------------------------- 1 | using BeatSaberMarkupLanguage; 2 | using HMUI; 3 | using PPPredictor.UI.ViewController; 4 | using PPPredictor.Utilities; 5 | 6 | namespace PPPredictor.UI 7 | { 8 | class PPPredictorFlowCoordinator : FlowCoordinator 9 | { 10 | private static PPPredictorFlowCoordinator instance = null; 11 | private static SettingsMidViewController settingsMidView; 12 | protected override void DidActivate(bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) 13 | { 14 | instance = this; 15 | SetTitle("PPPredictor"); 16 | showBackButton = true; 17 | settingsMidView = BeatSaberUI.CreateViewController(); 18 | ProvideInitialViewControllers(settingsMidView); 19 | } 20 | 21 | protected override void BackButtonWasPressed(HMUI.ViewController topViewController) => Close(); 22 | 23 | private void Close() 24 | { 25 | Plugin.pppViewController.ApplySettings(); 26 | ProfileInfoMgr.ParentFlow.DismissFlowCoordinator(instance, () => { 27 | instance = null; 28 | }, immediately: true); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PPPredictor/UI/Views/SettingsMidView.bsml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |