From 62294c99f0b15cf8a0da4844b477ae36c97358ce Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 13 Aug 2024 20:14:01 +0300 Subject: [PATCH 01/14] ported from master --- .../Components/ExtendedProfileScore.cs | 428 +++--------------- .../Components/LazerCalculationSettings.cs | 137 ++++++ .../Components/ProfileScore.cs | 381 ++++++++++++++++ .../Components/SettingsPopover.cs | 8 + .../Configuration/SettingsManager.cs | 5 +- PerformanceCalculatorGUI/RulesetHelper.cs | 65 +++ .../Screens/BeatmapLeaderboardScreen.cs | 20 +- .../Screens/LeaderboardScreen.cs | 10 +- .../Screens/ProfileScreen.cs | 342 +++++++++++++- 9 files changed, 1000 insertions(+), 396 deletions(-) create mode 100644 PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs create mode 100644 PerformanceCalculatorGUI/Components/ProfileScore.cs diff --git a/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs b/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs index e8aacd4e2..fb0a51d83 100644 --- a/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs +++ b/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs @@ -1,423 +1,115 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Framework.Localisation; -using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Leaderboards; -using osu.Game.Overlays; -using osu.Game.Overlays.Profile.Sections; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Utils; -using osuTK; -using PerformanceCalculatorGUI.Components.TextBoxes; namespace PerformanceCalculatorGUI.Components { - public class ExtendedScore + public class ExtendedProfileScore : ProfileScore { - public SoloScoreInfo SoloScore { get; } public double LivePP { get; } - - public Bindable Position { get; } = new Bindable(); public Bindable PositionChange { get; } = new Bindable(); - public PerformanceAttributes PerformanceAttributes { get; } - - public ExtendedScore(SoloScoreInfo score, double livePP, PerformanceAttributes attributes) + public ExtendedProfileScore(SoloScoreInfo score, double livePP, PerformanceAttributes attributes) + : base(score, attributes) { - SoloScore = score; - PerformanceAttributes = attributes; LivePP = livePP; } } - public partial class ExtendedProfileItemContainer : ProfileItemContainer + public partial class DrawableExtendedProfileScore : DrawableProfileScore { - public Action OnHoverAction { get; set; } - public Action OnUnhoverAction { get; set; } - - public ExtendedProfileItemContainer() - { - CornerRadius = ExtendedLabelledTextBox.CORNER_RADIUS; - } - - protected override bool OnHover(HoverEvent e) - { - OnHoverAction?.Invoke(); - return base.OnHover(e); - } + protected new ExtendedProfileScore Score { get; } - protected override void OnHoverLost(HoverLostEvent e) + public DrawableExtendedProfileScore(ExtendedProfileScore score) + : base(score) { - OnUnhoverAction?.Invoke(); - base.OnHoverLost(e); + Score = score; } - } - - public partial class ExtendedProfileScore : CompositeDrawable - { - private const int height = 40; - private const int performance_width = 100; - private const int rank_difference_width = 35; - private const int small_text_font_size = 11; - - private const float performance_background_shear = 0.45f; - - protected readonly ExtendedScore Score; - - [Resolved] - private OsuColour colours { get; set; } - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - private OsuSpriteText positionChangeText; - - public ExtendedProfileScore(ExtendedScore score) + protected override void LoadComplete() { - Score = score; + base.LoadComplete(); - RelativeSizeAxes = Axes.X; - Height = height; + Score.Position.UnbindEvents(); + Score.PositionChange.BindValueChanged(v => { PositionText.Text = $"{v.NewValue:+0;-0;-}"; }); } - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) + protected override Drawable[] CreateRightInfoContainerContent(RulesetStore rulesets) { - AddInternal(new ExtendedProfileItemContainer + return new Drawable[] { - OnHoverAction = () => - { - positionChangeText.Text = $"#{Score.Position.Value}"; - }, - OnUnhoverAction = () => + new FillFlowContainer { - positionChangeText.Text = $"{Score.PositionChange.Value:+0;-0;-}"; - }, - Children = new Drawable[] - { - new Container - { - Name = "Rank difference", - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = rank_difference_width, - Child = positionChangeText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colourProvider.Light1, - Text = Score.PositionChange.Value.ToString() - } - }, - new Container + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 60, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Name = "Score info", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = rank_difference_width, Right = performance_width }, - Children = new Drawable[] + new Container { - new FillFlowContainer + AutoSizeAxes = Axes.Y, + Child = new OsuSpriteText { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new UpdateableRank(Score.SoloScore.Rank) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(50, 20), - }, - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 0.5f), - Children = new Drawable[] - { - new ScoreBeatmapMetadataContainer(Score.SoloScore.Beatmap), - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), - Children = new Drawable[] - { - new OsuSpriteText - { - Text = $"{Score.SoloScore.Beatmap?.DifficultyName}", - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), - Colour = colours.Yellow - }, - new DrawableDate(Score.SoloScore.EndedAt, 12) - { - Colour = colourProvider.Foreground1 - } - } - } - } - } - } + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = $"{Score.LivePP:0}pp" }, - new FillFlowContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 110, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = Score.SoloScore.Accuracy.FormatAccuracy(), - Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true), - Colour = colours.Yellow, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }, - new OsuSpriteText - { - Text = $"{Score.SoloScore.MaxCombo}x {{ {formatStatistics(Score.SoloScore.Statistics)} }}", - Font = OsuFont.GetFont(size: small_text_font_size, weight: FontWeight.Regular), - Colour = colourProvider.Light2, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }, - } - }, - new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 60, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new Container - { - AutoSizeAxes = Axes.Y, - Child = new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = $"{Score.LivePP:0}pp" - }, - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: small_text_font_size), - Text = "live" - } - } - } - } - } - } - } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(2), - Children = Score.SoloScore.Mods.Select(mod => - { - var ruleset = rulesets.GetRuleset(Score.SoloScore.RulesetID) ?? throw new InvalidOperationException(); - - return new ModIcon(ruleset.CreateInstance().CreateModFromAcronym(mod.Acronym)!) - { - Scale = new Vector2(0.35f) - }; - }).ToList(), - } - } - } - } - }, - new Container - { - Name = "Performance", - RelativeSizeAxes = Axes.Y, - Width = performance_width, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Children = new Drawable[] + }, + new OsuSpriteText { - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Colour = colourProvider.Background4, - Shear = new Vector2(-performance_background_shear, 0), - EdgeSmoothness = new Vector2(2, 0), - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Y, - Height = -0.5f, - Position = new Vector2(0, 1), - Colour = colourProvider.Background4, - Shear = new Vector2(performance_background_shear, 0), - EdgeSmoothness = new Vector2(2, 0), - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Vertical = 5, - Left = 30, - Right = 20 - }, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new ExtendedOsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = $"{Score.SoloScore.PP:0}pp", - Colour = colourProvider.Highlight1, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - TooltipContent = $"{AttributeConversion.ToReadableString(Score.PerformanceAttributes)}" - }, - new OsuSpriteText - { - Font = OsuFont.GetFont(size: small_text_font_size), - Text = $"{Score.SoloScore.PP - Score.LivePP:+0.0;-0.0;-}", - Colour = colourProvider.Light1, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - } - } - } + Font = OsuFont.GetFont(size: SMALL_TEXT_FONT_SIZE), + Text = "live" } } } - }); - - Score.PositionChange.BindValueChanged(v => { positionChangeText.Text = $"{v.NewValue:+0;-0;-}"; }); - } - - private static string formatStatistics(Dictionary statistics) - { - // TODO: ruleset-specific display - return - $"{statistics.GetValueOrDefault(HitResult.Great)} / {statistics.GetValueOrDefault(HitResult.Ok)} / {statistics.GetValueOrDefault(HitResult.Meh)} / {statistics.GetValueOrDefault(HitResult.Miss)}"; + }.Concat(base.CreateRightInfoContainerContent(rulesets)).ToArray(); } - private partial class ScoreBeatmapMetadataContainer : OsuHoverContainer + protected override Drawable CreatePerformanceInfo() { - private readonly IBeatmapInfo beatmapInfo; - - public ScoreBeatmapMetadataContainer(IBeatmapInfo beatmapInfo) - { - this.beatmapInfo = beatmapInfo; - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader(true)] - private void load(GameHost host) + return new FillFlowContainer { - Action = () => + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { - host.OpenUrlExternally($"https://osu.ppy.sh/b/{beatmapInfo.OnlineID}"); - }; - - Child = new FillFlowContainer + Vertical = 5, + Left = 30, + Right = 20 + }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + new ExtendedOsuSpriteText { - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = new RomanisableString(beatmapInfo.Metadata.TitleUnicode, beatmapInfo.Metadata.Title), - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true) - }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = " by ", - Font = OsuFont.GetFont(size: 12, italics: true) - }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = new RomanisableString(beatmapInfo.Metadata.ArtistUnicode, beatmapInfo.Metadata.Artist), - Font = OsuFont.GetFont(size: 12, italics: true) - }, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = $"{Score.SoloScore.PP:0}pp", + Colour = ColourProvider.Highlight1, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + TooltipContent = $"{AttributeConversion.ToReadableString(Score.PerformanceAttributes)}" + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: SMALL_TEXT_FONT_SIZE), + Text = $"{Score.SoloScore.PP - Score.LivePP:+0.0;-0.0;-}", + Colour = ColourProvider.Light1, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre } - }; - } + } + }; } } } diff --git a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs new file mode 100644 index 000000000..472d65edb --- /dev/null +++ b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs @@ -0,0 +1,137 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK; +using osu.Framework.Extensions; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Overlays.Toolbar; +using osu.Framework.Bindables; +using osu.Game.Scoring; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Mods; + +namespace PerformanceCalculatorGUI.Components +{ + public partial class LazerCalculationSettings : ToolbarButton, IHasPopover + { + private readonly Bindable calculateRankedMaps = new(true); + private readonly Bindable calculateUnrankedMaps = new(false); + + private readonly Bindable calculateUnsubmittedScores = new(true); + private readonly Bindable calculateUnrankedMods = new(true); + + private readonly Bindable enableScorev1Overwrite = new(false); + public bool IsScorev1OverwritingEnabled => enableScorev1Overwrite.Value; + protected override Anchor TooltipAnchor => Anchor.TopRight; + + public LazerCalculationSettings() + { + TooltipMain = "Calculation Settings"; + + SetIcon(new ScreenSelectionButtonIcon(FontAwesome.Solid.Cog) { IconSize = new Vector2(70) }); + } + + public bool ShouldBeFiltered(ScoreInfo score) + { + if (score.Mods.Any(h => h is OsuModRelax || h is OsuModAutopilot)) + return true; + + if (!calculateRankedMaps.Value && score.BeatmapInfo.Status.GrantsPerformancePoints()) + return true; + + if (!calculateUnrankedMaps.Value && !score.BeatmapInfo.Status.GrantsPerformancePoints()) + return true; + + if (!calculateUnrankedMods.Value) + { + // Check for legacy score because CL is unranked + if (!score.Mods.All(m => m.Ranked || (score.IsLegacyScore && m is OsuModClassic))) + return true; + } + + if (!calculateUnsubmittedScores.Value) + { + if (score.OnlineID <= 0 && score.LegacyOnlineID <= 0) + return true; + } + + return false; + } + + public Popover GetPopover() => new LazerCalculationSettingsPopover( + new[] { calculateRankedMaps, calculateUnrankedMaps, calculateUnsubmittedScores, calculateUnrankedMods, enableScorev1Overwrite }); + + protected override bool OnClick(ClickEvent e) + { + this.ShowPopover(); + return base.OnClick(e); + } + } + + public partial class LazerCalculationSettingsPopover : OsuPopover + { + private readonly Bindable[] bindables; + + public LazerCalculationSettingsPopover(Bindable[] bindables) + { + this.bindables = bindables; + } + + [BackgroundDependencyLoader] + private void load() + { + Add(new Container + { + AutoSizeAxes = Axes.Y, + Width = 500, + Children = new Drawable[] + { + new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(18), + Children = new Drawable[] + { + new OsuCheckbox + { + LabelText = "Calculate Ranked Maps (ranked & approved)", + Current = { BindTarget = bindables[0] } + }, + new OsuCheckbox + { + LabelText = "Calculate Unranked Maps (everything else)", + Current = { BindTarget = bindables[1] } + }, + new OsuCheckbox + { + LabelText = "Calculate Unsubmitted Scores (on local difficulties for example)", + Current = { BindTarget = bindables[2] } + }, + new OsuCheckbox + { + LabelText = "Calculate Unranked Mods (RX and AP are excluded regardless)", + Current = { BindTarget = bindables[3] } + }, + new OsuCheckbox + { + LabelText = "Enable Scorev1 score overwrite for legacy scores", + Current = { BindTarget = bindables[4] } + }, + } + } + } + }); + } + } +} diff --git a/PerformanceCalculatorGUI/Components/ProfileScore.cs b/PerformanceCalculatorGUI/Components/ProfileScore.cs new file mode 100644 index 000000000..1202ba7e3 --- /dev/null +++ b/PerformanceCalculatorGUI/Components/ProfileScore.cs @@ -0,0 +1,381 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; +using osu.Game.Overlays; +using osu.Game.Overlays.Profile.Sections; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Utils; +using osuTK; +using PerformanceCalculatorGUI.Components.TextBoxes; + +namespace PerformanceCalculatorGUI.Components +{ + public class ProfileScore + { + public SoloScoreInfo SoloScore { get; } + + public Bindable Position { get; } = new Bindable(); + + public PerformanceAttributes PerformanceAttributes { get; } + + public ProfileScore(SoloScoreInfo score, PerformanceAttributes attributes) + { + SoloScore = score; + PerformanceAttributes = attributes; + } + + public ProfileScore(ScoreInfo score, PerformanceAttributes attributes) + { + SoloScore = toSoloScoreInfo(score); + PerformanceAttributes = attributes; + } + + private static SoloScoreInfo toSoloScoreInfo(ScoreInfo score) + { + APIBeatmapSet dummySet = new APIBeatmapSet + { + Title = score.BeatmapInfo.Metadata.Title, + TitleUnicode = score.BeatmapInfo.Metadata.TitleUnicode, + Artist = score.BeatmapInfo.Metadata.Artist, + ArtistUnicode = score.BeatmapInfo.Metadata.ArtistUnicode, + }; + APIBeatmap dummyBeatmap = new APIBeatmap + { + OnlineID = score.BeatmapInfo.OnlineID, + DifficultyName = score.BeatmapInfo.DifficultyName, + }; + SoloScoreInfo soloScoreInfo = new SoloScoreInfo + { + PP = score.PP, + Accuracy = score.Accuracy, + Rank = score.Rank, + Statistics = score.Statistics, + MaxCombo = score.MaxCombo, + Mods = score.APIMods, + Beatmap = dummyBeatmap, + EndedAt = score.Date, + BeatmapSet = dummySet, + }; + + return soloScoreInfo; + } + + } + + public partial class DrawableProfileScore : CompositeDrawable + { + private const int height = 40; + private const int rank_difference_width = 35; + private const int performance_width = 100; + private const int rank_width = 35; + + protected const int SMALL_TEXT_FONT_SIZE = 11; + + private const float performance_background_shear = 0.45f; + + protected FillFlowContainer RightInfoContainer; + + protected ProfileScore Score { get; } + + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + protected OverlayColourProvider ColourProvider { get; private set; } + + protected OsuSpriteText PositionText; + + public DrawableProfileScore(ProfileScore score) + { + Score = score; + + RelativeSizeAxes = Axes.X; + Height = height; + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + AddInternal(new ProfileItemContainer + { + RelativeSizeAxes = Axes.Both, + CornerRadius = ExtendedLabelledTextBox.CORNER_RADIUS, + Children = new Drawable[] + { + new Container + { + Name = "Rank difference", + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = rank_width, + Children = new Drawable[] + { + PositionText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = ColourProvider.Light1, + Text = Score.Position.Value.ToString() + } + } + }, + new Container + { + Name = "Score info", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = rank_difference_width, Right = performance_width }, + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new UpdateableRank(Score.SoloScore.Rank) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(50, 20), + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 0.5f), + Children = new Drawable[] + { + new ScoreBeatmapMetadataContainer(Score.SoloScore.Beatmap), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(15, 0), + Children = new Drawable[] + { + new OsuSpriteText + { + Text = $"{Score.SoloScore.Beatmap?.DifficultyName}", + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + Colour = colours.Yellow + }, + new DrawableDate(Score.SoloScore.EndedAt, 12) + { + Colour = ColourProvider.Foreground1 + } + } + } + } + } + } + }, + RightInfoContainer = new FillFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Right = 10 }, + Children = CreateRightInfoContainerContent(rulesets) + } + } + }, + new Container + { + Name = "Performance", + RelativeSizeAxes = Axes.Y, + Width = performance_width, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Colour = ColourProvider.Background4, + Shear = new Vector2(-performance_background_shear, 0), + EdgeSmoothness = new Vector2(2, 0), + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Y, + Height = -0.5f, + Position = new Vector2(0, 1), + Colour = ColourProvider.Background4, + Shear = new Vector2(performance_background_shear, 0), + EdgeSmoothness = new Vector2(2, 0), + }, + CreatePerformanceInfo() + } + } + } + }); + } + + protected virtual Drawable[] CreateRightInfoContainerContent(RulesetStore rulesets) + { + return new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 110, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = Score.SoloScore.Accuracy.FormatAccuracy(), + Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true), + Colour = colours.Yellow, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + new OsuSpriteText + { + Text = $"{Score.SoloScore.MaxCombo}x {{ {formatStatistics(Score.SoloScore.Statistics)} }}", + Font = OsuFont.GetFont(size: SMALL_TEXT_FONT_SIZE, weight: FontWeight.Regular), + Colour = ColourProvider.Light2, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + } + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2), + Children = Score.SoloScore.Mods.Select(mod => + { + var ruleset = rulesets.GetRuleset(Score.SoloScore.RulesetID) ?? throw new InvalidOperationException(); + return new ModIcon(mod.ToMod(ruleset.CreateInstance())) + { + Scale = new Vector2(0.35f) + }; + }).ToList(), + } + }; + } + + protected virtual Drawable CreatePerformanceInfo() + { + return new ExtendedOsuSpriteText + { + Padding = new MarginPadding + { + Vertical = 5, + Left = 30, + Right = 20 + }, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = $"{Score.SoloScore.PP:0}pp", + Colour = ColourProvider.Highlight1, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TooltipContent = $"{AttributeConversion.ToReadableString(Score.PerformanceAttributes)}" + }; + } + + private static string formatStatistics(Dictionary statistics) + { + // TODO: ruleset-specific display + return + $"{statistics.GetValueOrDefault(HitResult.Great)} / {statistics.GetValueOrDefault(HitResult.Ok)} / {statistics.GetValueOrDefault(HitResult.Meh)} / {statistics.GetValueOrDefault(HitResult.Miss)}"; + } + + private partial class ScoreBeatmapMetadataContainer : OsuHoverContainer + { + private readonly IBeatmapInfo beatmapInfo; + + public ScoreBeatmapMetadataContainer(IBeatmapInfo beatmapInfo) + { + this.beatmapInfo = beatmapInfo; + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader(true)] + private void load(GameHost host) + { + Action = () => + { + host.OpenUrlExternally($"https://osu.ppy.sh/b/{beatmapInfo.OnlineID}"); + }; + + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = new RomanisableString(beatmapInfo.Metadata.TitleUnicode, beatmapInfo.Metadata.Title), + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true) + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = " by ", + Font = OsuFont.GetFont(size: 12, italics: true) + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = new RomanisableString(beatmapInfo.Metadata.ArtistUnicode, beatmapInfo.Metadata.Artist), + Font = OsuFont.GetFont(size: 12, italics: true) + }, + } + }; + } + } + } +} diff --git a/PerformanceCalculatorGUI/Components/SettingsPopover.cs b/PerformanceCalculatorGUI/Components/SettingsPopover.cs index 8cca41cab..6f43b4ec0 100644 --- a/PerformanceCalculatorGUI/Components/SettingsPopover.cs +++ b/PerformanceCalculatorGUI/Components/SettingsPopover.cs @@ -28,6 +28,7 @@ public partial class SettingsPopover : OsuPopover private Bindable clientSecretBindable; private Bindable pathBindable; private Bindable cacheBindable; + private Bindable lazerPathBindable; private Bindable scaleBindable; private const string api_key_link = "https://osu.ppy.sh/home/account/edit#new-oauth-application"; @@ -40,6 +41,7 @@ private void load(SettingsManager configManager, OsuConfigManager osuConfig) clientSecretBindable = configManager.GetBindable(Settings.ClientSecret); pathBindable = configManager.GetBindable(Settings.DefaultPath); cacheBindable = configManager.GetBindable(Settings.CachePath); + lazerPathBindable = configManager.GetBindable(Settings.LazerFolderPath); scaleBindable = osuConfig.GetBindable(OsuSetting.UIScale); Add(new Container @@ -95,6 +97,12 @@ private void load(SettingsManager configManager, OsuConfigManager osuConfig) Label = "Beatmap cache path", Current = { BindTarget = cacheBindable } }, + new LabelledTextBox + { + RelativeSizeAxes = Axes.X, + Label = "Lazer folder path", + Current = { BindTarget = lazerPathBindable } + }, new Box { RelativeSizeAxes = Axes.X, diff --git a/PerformanceCalculatorGUI/Configuration/SettingsManager.cs b/PerformanceCalculatorGUI/Configuration/SettingsManager.cs index 19e0447c0..61da87871 100644 --- a/PerformanceCalculatorGUI/Configuration/SettingsManager.cs +++ b/PerformanceCalculatorGUI/Configuration/SettingsManager.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Reflection; using osu.Framework.Configuration; @@ -13,7 +14,8 @@ public enum Settings ClientId, ClientSecret, DefaultPath, - CachePath + CachePath, + LazerFolderPath } public class SettingsManager : IniConfigManager @@ -31,6 +33,7 @@ protected override void InitialiseDefaults() SetDefault(Settings.ClientSecret, string.Empty); SetDefault(Settings.DefaultPath, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); SetDefault(Settings.CachePath, Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "cache")); + SetDefault(Settings.LazerFolderPath, string.Empty); } } } diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index c99ad0b9d..efaaf66ea 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -13,8 +13,10 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko.Objects; @@ -87,6 +89,69 @@ public static Ruleset GetRulesetFromLegacyID(int id) }; } + /// + /// Generates the unique hash of mods combo that affect difficulty calculation + /// Needs to be updated if list of difficulty adjusting mods changes + /// + public static int GenerateModsHash(Mod[] mods, BeatmapDifficulty difficulty, RulesetInfo ruleset) + { + // Rate changing mods + double rate = ModUtils.CalculateRateWithMods(mods); + + int hash = 0; + + if (ruleset.OnlineID == 0) // For osu we have many different things + { + BeatmapDifficulty d = new BeatmapDifficulty(difficulty); + + foreach (var mod in mods.OfType()) + mod.ApplyToDifficulty(d); + + bool isSliderAccuracy = mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value); + + byte flashlightHash = 0; + if (mods.Any(h => h is OsuModFlashlight)) + { + if (!mods.Any(h => h is OsuModHidden)) flashlightHash = 1; + else flashlightHash = 2; + } + + byte mirrorHash = 0; + if (mods.FirstOrDefault(m => m is OsuModMirror) is OsuModMirror mirror) + { + mirrorHash = (byte)(1 + (int)(mirror.Reflection.Value)); + } + + hash = HashCode.Combine(rate, d.CircleSize, d.OverallDifficulty, isSliderAccuracy, flashlightHash, mirrorHash); + } + else if (ruleset.OnlineID == 1) // For taiko we only have rate + { + hash = rate.GetHashCode(); + } + else if (ruleset.OnlineID == 2) // For catch we have rate and CS + { + BeatmapDifficulty d = new BeatmapDifficulty(difficulty); + + foreach (var mod in mods.OfType()) + mod.ApplyToDifficulty(d); + + hash = HashCode.Combine(rate, d.CircleSize); + } + else if (ruleset.OnlineID == 3) // Mania is using rate, and keys data for converts + { + int keyCount = 0; + + if (mods.FirstOrDefault(h => h is ManiaKeyMod) is ManiaKeyMod mod) + keyCount = mod.KeyCount; + + bool isDualStages = mods.Any(h => h is ManiaModDualStages); + + hash = HashCode.Combine(rate, keyCount, isDualStages); + } + + return hash; + } + public static int AdjustManiaScore(int score, IReadOnlyList mods) { if (score != 1000000) return score; diff --git a/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs b/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs index 00a8334f5..90ef85c85 100644 --- a/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs +++ b/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs @@ -19,6 +19,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using PerformanceCalculatorGUI.Components; @@ -219,6 +220,10 @@ private void calculate() if (leaderboard.Scores.Count == 0) return; + var difficultyCalculator = rulesetInstance.CreateDifficultyCalculator(working); + + Dictionary attributesCache = new(); + foreach (var score in leaderboard.Scores) { if (token.IsCancellationRequested) @@ -227,14 +232,23 @@ private void calculate() Schedule(() => loadingLayer.Text.Value = $"Calculating {score.User?.Username}"); var scoreInfo = score.ToScoreInfo(rulesets, working.BeatmapInfo); + Mod[] mods = score.Mods.Select(x => x.ToMod(rulesetInstance)).ToArray(); var parsedScore = new ProcessorScoreDecoder(working).Parse(scoreInfo); - var difficultyCalculator = rulesetInstance.CreateDifficultyCalculator(working); + DifficultyAttributes difficultyAttributes; + int modsHash = RulesetHelper.GenerateModsHash(mods, working.BeatmapInfo.Difficulty, ruleset.Value); - Mod[] mods = score.Mods.Select(x => x.ToMod(rulesetInstance)).ToArray(); + if (attributesCache.ContainsKey(modsHash)) + { + difficultyAttributes = attributesCache[modsHash]; + } + else + { + difficultyAttributes = difficultyCalculator.Calculate(mods); + attributesCache[modsHash] = difficultyAttributes; + } - var difficultyAttributes = difficultyCalculator.Calculate(mods); var performanceCalculator = rulesetInstance.CreatePerformanceCalculator(); var perfAttributes = await performanceCalculator?.CalculateAsync(parsedScore.ScoreInfo, difficultyAttributes, token)!; diff --git a/PerformanceCalculatorGUI/Screens/LeaderboardScreen.cs b/PerformanceCalculatorGUI/Screens/LeaderboardScreen.cs index c61a41c46..aeea63733 100644 --- a/PerformanceCalculatorGUI/Screens/LeaderboardScreen.cs +++ b/PerformanceCalculatorGUI/Screens/LeaderboardScreen.cs @@ -31,7 +31,7 @@ public class UserLeaderboardData public decimal LivePP { get; set; } public decimal LocalPP { get; set; } - public List Scores { get; set; } + public List Scores { get; set; } } public partial class LeaderboardScreen : PerformanceCalculatorScreen @@ -242,7 +242,7 @@ private void calculate() var leaderboard = await apiManager.GetJsonFromApi($"rankings/{ruleset.Value.ShortName}/performance?cursor[page]={pageTextBox.Value.Value - 1}"); var calculatedPlayers = new List(); - var calculatedScores = new List(); + var calculatedScores = new List(); for (int i = 0; i < playerAmountTextBox.Value.Value; i++) { @@ -277,7 +277,7 @@ private void calculate() foreach (var calculatedScore in calculatedScores.OrderByDescending(x => x.PerformanceAttributes.Total)) { - scores.Add(new ExtendedProfileScore(calculatedScore)); + scores.Add(new DrawableExtendedProfileScore(calculatedScore)); } }); }, token).ContinueWith(t => @@ -299,7 +299,7 @@ private async Task calculatePlayer(UserStatistics player, C if (token.IsCancellationRequested) return new UserLeaderboardData(); - var plays = new List(); + var plays = new List(); var apiScores = await apiManager.GetJsonFromApi>($"users/{player.User.OnlineID}/scores/best?mode={ruleset.Value.ShortName}&limit=100"); @@ -327,7 +327,7 @@ private async Task calculatePlayer(UserStatistics player, C var perfAttributes = performanceCalculator?.Calculate(parsedScore.ScoreInfo, difficultyAttributes); score.PP = perfAttributes?.Total ?? 0.0; - var extendedScore = new ExtendedScore(score, livePp, perfAttributes); + var extendedScore = new ExtendedProfileScore(score, livePp, perfAttributes); plays.Add(extendedScore); } catch (Exception e) diff --git a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs index e46a56381..834deba06 100644 --- a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs +++ b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs @@ -6,22 +6,27 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osuTK.Graphics; using PerformanceCalculatorGUI.Components; using PerformanceCalculatorGUI.Components.TextBoxes; using PerformanceCalculatorGUI.Configuration; +using System.IO; +using osu.Framework.Platform; namespace PerformanceCalculatorGUI.Screens { @@ -30,7 +35,6 @@ public partial class ProfileScreen : PerformanceCalculatorScreen [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); - private StatefulButton calculationButton; private VerboseLoadingLayer loadingLayer; private GridContainer layout; @@ -41,7 +45,16 @@ public partial class ProfileScreen : PerformanceCalculatorScreen private Container userPanelContainer; private UserCard userPanel; - private string currentUser; + private GridContainer setupContainer; + private SwitchButton profileImportTypeSwitch; + + private StatefulButton calculationButtonServer; + + private GridContainer localCalcSetupContainer; + private StatefulButton calculationButtonLocal; + private LazerCalculationSettings settingsMenu; + + private string[] currentUser; private CancellationTokenSource calculationCancellatonToken; @@ -60,8 +73,12 @@ public partial class ProfileScreen : PerformanceCalculatorScreen [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private GameHost gameHost { get; set; } + public override bool ShouldShowConfirmationDialogOnSwitch => false; + private const float setup_width = 220; private const float username_container_height = 40; public ProfileScreen() @@ -72,6 +89,41 @@ public ProfileScreen() [BackgroundDependencyLoader] private void load() { + calculationButtonServer = new StatefulButton("Calculate from server") + { + Width = setup_width, + Height = username_container_height, + Action = () => { calculateProfileFromServer(usernameTextBox.Current.Value); } + }; + + localCalcSetupContainer = new GridContainer + { + Width = setup_width, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + calculationButtonLocal = new StatefulButton("Calculate from lazer") + { + RelativeSizeAxes = Axes.X, + Height = username_container_height, + Action = () => { calculateProfileFromLazer(usernameTextBox.Current.Value); } + }, + + settingsMenu = new LazerCalculationSettings() + } + } + }; + InternalChildren = new Drawable[] { layout = new GridContainer @@ -83,14 +135,15 @@ private void load() { new Drawable[] { - new GridContainer + setupContainer = new GridContainer { - Name = "Settings", + Name = "Setup", Height = username_container_height, RelativeSizeAxes = Axes.X, ColumnDimensions = new[] { new Dimension(), + new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize) }, RowDimensions = new[] @@ -109,12 +162,12 @@ private void load() PlaceholderText = "peppy", CommitOnFocusLoss = false }, - calculationButton = new StatefulButton("Start calculation") + profileImportTypeSwitch = new SwitchButton { - Width = 150, - Height = username_container_height, - Action = () => { calculateProfile(usernameTextBox.Current.Value); } - } + Width = 80, + Height = username_container_height + }, + calculationButtonServer } } }, @@ -148,13 +201,61 @@ private void load() } }; - usernameTextBox.OnCommit += (_, _) => { calculateProfile(usernameTextBox.Current.Value); }; + profileImportTypeSwitch.Current.BindValueChanged(val => + { + calculationCancellatonToken?.Cancel(); + + if (val.NewValue) + { + setupContainer.ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }; + setupContainer.Content = new[] + { + new Drawable[] + { + usernameTextBox, + profileImportTypeSwitch, + localCalcSetupContainer + } + }; + } + else + { + setupContainer.ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }; + setupContainer.Content = new[] + { + new Drawable[] + { + usernameTextBox, + profileImportTypeSwitch, + calculationButtonServer + } + }; + } + }); - if (RuntimeInfo.IsDesktop) - HotReloadCallbackReceiver.CompilationFinished += _ => Schedule(() => { calculateProfile(currentUser); }); + usernameTextBox.OnCommit += (_, _) => { calculateProfile(usernameTextBox.Current.Value); }; } private void calculateProfile(string username) + { + if (profileImportTypeSwitch.Current.Value) + calculateProfileFromLazer(username); + else + calculateProfileFromServer(username); + } + + private void calculateProfileFromServer(string username) { if (string.IsNullOrEmpty(username)) { @@ -166,7 +267,7 @@ private void calculateProfile(string username) calculationCancellatonToken?.Dispose(); loadingLayer.Show(); - calculationButton.State.Value = ButtonState.Loading; + calculationButtonServer.State.Value = ButtonState.Loading; scores.Clear(); @@ -179,7 +280,7 @@ private void calculateProfile(string username) var player = await apiManager.GetJsonFromApi($"users/{username}/{ruleset.Value.ShortName}"); - currentUser = player.Username; + currentUser = [player.Username]; Schedule(() => { @@ -197,7 +298,7 @@ private void calculateProfile(string username) if (token.IsCancellationRequested) return; - var plays = new List(); + var plays = new List(); var rulesetInstance = ruleset.Value.CreateInstance(); @@ -228,10 +329,10 @@ private void calculateProfile(string username) var perfAttributes = await performanceCalculator?.CalculateAsync(parsedScore.ScoreInfo, difficultyAttributes, token)!; score.PP = perfAttributes?.Total ?? 0.0; - var extendedScore = new ExtendedScore(score, livePp, perfAttributes); + var extendedScore = new ExtendedProfileScore(score, livePp, perfAttributes); plays.Add(extendedScore); - Schedule(() => scores.Add(new ExtendedProfileScore(extendedScore))); + Schedule(() => scores.Add(new DrawableExtendedProfileScore(extendedScore))); } if (token.IsCancellationRequested) @@ -282,11 +383,214 @@ private void calculateProfile(string username) Schedule(() => { loadingLayer.Hide(); - calculationButton.State.Value = ButtonState.Done; + calculationButtonServer.State.Value = ButtonState.Done; }); }, token); } + private void calculateProfileFromLazer(string username) + { + if (string.IsNullOrEmpty(username)) + { + usernameTextBox.FlashColour(Color4.Red, 1); + return; + } + + calculationCancellatonToken?.Cancel(); + calculationCancellatonToken?.Dispose(); + + loadingLayer.Show(); + calculationButtonLocal.State.Value = ButtonState.Loading; + + scores.Clear(); + + calculationCancellatonToken = new CancellationTokenSource(); + var token = calculationCancellatonToken.Token; + + Task.Run(async () => + { + Schedule(() => loadingLayer.Text.Value = "Getting user data..."); + + var player = await apiManager.GetJsonFromApi($"users/{username}/{ruleset.Value.ShortName}"); + + currentUser = [player.Username, .. player.PreviousUsernames, player.Id.ToString()]; + + Schedule(() => + { + if (userPanel != null) + userPanelContainer.Remove(userPanel, true); + + userPanelContainer.Add(userPanel = new UserCard(player) + { + RelativeSizeAxes = Axes.X + }); + + layout.RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, username_container_height), new Dimension(GridSizeMode.AutoSize), new Dimension() }; + }); + + if (token.IsCancellationRequested) + return; + + var plays = new List(); + + var rulesetInstance = ruleset.Value.CreateInstance(); + + var lazerPath = configManager.GetBindable(Settings.LazerFolderPath).Value; + + if (lazerPath == string.Empty) + { + notificationDisplay.Display(new Notification("Please set-up path to lazer database folder in GUI settings")); + return; + } + + var storage = gameHost.GetStorage(lazerPath); + var realmAccess = new RealmAccess(storage, @"client.realm"); + + var realmScores = getRealmScores(realmAccess); + + int currentScoresCount = 0; + var totalScoresCount = realmScores.Sum(childList => childList.Count); + + foreach (var scoreList in realmScores) + { + string beatmapHash = scoreList[0].BeatmapHash; + //get the .osu file from lazer file storage + var working = new FlatWorkingBeatmap(Path.Combine(lazerPath, "files", beatmapHash[..1], beatmapHash[..2], beatmapHash)); + + var difficultyCalculator = rulesetInstance.CreateDifficultyCalculator(working); + var performanceCalculator = rulesetInstance.CreatePerformanceCalculator(); + + List tempScores = []; + + Dictionary attributesCache = new(); + + foreach (var score in scoreList) + { + if (token.IsCancellationRequested) + return; + + Schedule(() => loadingLayer.Text.Value = $"Calculating {player.Username}'s scores... {currentScoresCount} / {totalScoresCount}"); + + DifficultyAttributes difficultyAttributes; + int modsHash = RulesetHelper.GenerateModsHash(score.Mods, working.BeatmapInfo.Difficulty, ruleset.Value); + + if (attributesCache.ContainsKey(modsHash)) + { + difficultyAttributes = attributesCache[modsHash]; + } + else + { + difficultyAttributes = difficultyCalculator.Calculate(score.Mods); + attributesCache[modsHash] = difficultyAttributes; + } + + var perfAttributes = await performanceCalculator?.CalculateAsync(score, difficultyAttributes, token)!; + + score.PP = perfAttributes?.Total ?? 0.0; + + currentScoresCount++; + + // Sanity check for aspire maps till my slider fix won't get merged + if (difficultyAttributes.StarRating > 14 && score.BeatmapInfo.Status != BeatmapOnlineStatus.Ranked) + continue; + + tempScores.Add(new ProfileScore(score, perfAttributes)); + } + + var topScore = tempScores.MaxBy(s => s.SoloScore.PP); + if (topScore == null) + continue; + + plays.Add(topScore); + Schedule(() => scores.Add(new DrawableProfileScore(topScore))); + } + + if (token.IsCancellationRequested) + return; + + var localOrdered = plays.OrderByDescending(x => x.SoloScore.PP).ToList(); + + Schedule(() => + { + foreach (var play in plays) + { + play.Position.Value = localOrdered.IndexOf(play) + 1; + scores.SetLayoutPosition(scores[plays.IndexOf(play)], localOrdered.IndexOf(play)); + } + }); + + decimal totalLocalPP = 0; + for (var i = 0; i < localOrdered.Count; i++) + totalLocalPP += (decimal)(Math.Pow(0.95, i) * (localOrdered[i].SoloScore.PP ?? 0)); + + decimal totalLivePP = player.Statistics.PP ?? (decimal)0.0; + + //Calculate bonusPP based of unique score count on ranked diffs + var playcountBonusPP = (decimal)((417.0 - 1.0 / 3.0) * (1 - Math.Pow(0.995, Math.Min(realmScores.Count, 1000)))); + totalLocalPP += playcountBonusPP; + + Schedule(() => + { + userPanel.Data.Value = new UserCardData + { + LivePP = totalLivePP, + LocalPP = totalLocalPP, + PlaycountPP = playcountBonusPP + }; + }); + }, token).ContinueWith(t => + { + Logger.Log(t.Exception?.ToString(), level: LogLevel.Error); + notificationDisplay.Display(new Notification(t.Exception?.Flatten().Message)); + }, TaskContinuationOptions.OnlyOnFaulted).ContinueWith(t => + { + Schedule(() => + { + loadingLayer.Hide(); + calculationButtonLocal.State.Value = ButtonState.Done; + }); + }, token); + } + + private List> getRealmScores(RealmAccess realm) + { + Schedule(() => loadingLayer.Text.Value = "Extracting user scores..."); + var realmScores = realm.Run(r => r.All().Detach()); + + Schedule(() => loadingLayer.Text.Value = "Filtering scores..."); + + realmScores.RemoveAll(x => !currentUser.Contains(x.User.Username) // Wrong username + || x.BeatmapInfo == null // No map for score + || x.Passed == false || x.Rank == ScoreRank.F // Failed score + || x.Ruleset.OnlineID != ruleset.Value.OnlineID // Incorrect ruleset + || settingsMenu.ShouldBeFiltered(x)); // Customisable filters + + List> groupedScores = realmScores.GroupBy(g => g.BeatmapHash) + .Select(s => s.ToList()) + .ToList(); + // Simulate scorev1 if enabled + if (settingsMenu.IsScorev1OverwritingEnabled) + { + var rulesetInstance = ruleset.Value.CreateInstance(); + + List> filteredScores = new(); + + foreach (var mapScores in groupedScores) + { + List filteredMapScores = mapScores.Where(s => s.IsLegacyScore) + .GroupBy(x => rulesetInstance.ConvertToLegacyMods(x.Mods)) + .Select(x => x.MaxBy(x => x.LegacyTotalScore)) + .ToList(); + filteredMapScores.AddRange(mapScores.Where(s => !s.IsLegacyScore)); + filteredScores.Add(mapScores); + } + + groupedScores = filteredScores; + } + + return groupedScores; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 8ec4ed2bd2dec017fb2170798510045c1b3bba12 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 13 Aug 2024 20:20:16 +0300 Subject: [PATCH 02/14] Update LazerCalculationSettings.cs --- PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs index 472d65edb..98a87f22f 100644 --- a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs +++ b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs @@ -30,7 +30,9 @@ public partial class LazerCalculationSettings : ToolbarButton, IHasPopover private readonly Bindable calculateUnrankedMods = new(true); private readonly Bindable enableScorev1Overwrite = new(false); + public bool IsScorev1OverwritingEnabled => enableScorev1Overwrite.Value; + protected override Anchor TooltipAnchor => Anchor.TopRight; public LazerCalculationSettings() From 1c81765298fa7824210b33f20344c1c6461c79ad Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 13 Aug 2024 20:41:15 +0300 Subject: [PATCH 03/14] fixed CI --- .../Components/LazerCalculationSettings.cs | 13 +++++--- .../Components/ProfileScore.cs | 15 ++++----- .../Configuration/SettingsManager.cs | 1 - PerformanceCalculatorGUI/RulesetHelper.cs | 5 +-- .../Screens/BeatmapLeaderboardScreen.cs | 9 ++--- .../Screens/ProfileScreen.cs | 33 ++++++++----------- 6 files changed, 34 insertions(+), 42 deletions(-) diff --git a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs index 98a87f22f..51bec38ea 100644 --- a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs +++ b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs @@ -23,13 +23,13 @@ namespace PerformanceCalculatorGUI.Components { public partial class LazerCalculationSettings : ToolbarButton, IHasPopover { - private readonly Bindable calculateRankedMaps = new(true); - private readonly Bindable calculateUnrankedMaps = new(false); + private readonly Bindable calculateRankedMaps = new Bindable(true); + private readonly Bindable calculateUnrankedMaps = new Bindable(false); - private readonly Bindable calculateUnsubmittedScores = new(true); - private readonly Bindable calculateUnrankedMods = new(true); + private readonly Bindable calculateUnsubmittedScores = new Bindable(true); + private readonly Bindable calculateUnrankedMods = new Bindable(true); - private readonly Bindable enableScorev1Overwrite = new(false); + private readonly Bindable enableScorev1Overwrite = new Bindable(false); public bool IsScorev1OverwritingEnabled => enableScorev1Overwrite.Value; @@ -47,6 +47,9 @@ public bool ShouldBeFiltered(ScoreInfo score) if (score.Mods.Any(h => h is OsuModRelax || h is OsuModAutopilot)) return true; + if (score.BeatmapInfo == null) + return true; + if (!calculateRankedMaps.Value && score.BeatmapInfo.Status.GrantsPerformancePoints()) return true; diff --git a/PerformanceCalculatorGUI/Components/ProfileScore.cs b/PerformanceCalculatorGUI/Components/ProfileScore.cs index 1202ba7e3..7c8813581 100644 --- a/PerformanceCalculatorGUI/Components/ProfileScore.cs +++ b/PerformanceCalculatorGUI/Components/ProfileScore.cs @@ -54,15 +54,15 @@ private static SoloScoreInfo toSoloScoreInfo(ScoreInfo score) { APIBeatmapSet dummySet = new APIBeatmapSet { - Title = score.BeatmapInfo.Metadata.Title, - TitleUnicode = score.BeatmapInfo.Metadata.TitleUnicode, - Artist = score.BeatmapInfo.Metadata.Artist, - ArtistUnicode = score.BeatmapInfo.Metadata.ArtistUnicode, + Title = score.BeatmapInfo?.Metadata.Title ?? "Error Title", + TitleUnicode = score.BeatmapInfo?.Metadata.TitleUnicode ?? "Error Title", + Artist = score.BeatmapInfo?.Metadata.Artist ?? "Error Artist", + ArtistUnicode = score.BeatmapInfo?.Metadata.ArtistUnicode ?? "Error Artist", }; APIBeatmap dummyBeatmap = new APIBeatmap { - OnlineID = score.BeatmapInfo.OnlineID, - DifficultyName = score.BeatmapInfo.DifficultyName, + OnlineID = score.BeatmapInfo?.OnlineID ?? 0, + DifficultyName = score.BeatmapInfo?.DifficultyName ?? "Error Difficulty", }; SoloScoreInfo soloScoreInfo = new SoloScoreInfo { @@ -79,7 +79,6 @@ private static SoloScoreInfo toSoloScoreInfo(ScoreInfo score) return soloScoreInfo; } - } public partial class DrawableProfileScore : CompositeDrawable @@ -214,7 +213,7 @@ private void load(RulesetStore rulesets) Width = performance_width, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Children = new Drawable[] + Children = new[] { new Box { diff --git a/PerformanceCalculatorGUI/Configuration/SettingsManager.cs b/PerformanceCalculatorGUI/Configuration/SettingsManager.cs index 61da87871..506430cad 100644 --- a/PerformanceCalculatorGUI/Configuration/SettingsManager.cs +++ b/PerformanceCalculatorGUI/Configuration/SettingsManager.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.IO; using System.Reflection; using osu.Framework.Configuration; diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index efaaf66ea..75c4d9a2f 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -110,13 +110,14 @@ public static int GenerateModsHash(Mod[] mods, BeatmapDifficulty difficulty, Rul bool isSliderAccuracy = mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value); byte flashlightHash = 0; + if (mods.Any(h => h is OsuModFlashlight)) { - if (!mods.Any(h => h is OsuModHidden)) flashlightHash = 1; - else flashlightHash = 2; + flashlightHash = (byte)(mods.Any(h => h is OsuModHidden) ? 2 : 1); } byte mirrorHash = 0; + if (mods.FirstOrDefault(m => m is OsuModMirror) is OsuModMirror mirror) { mirrorHash = (byte)(1 + (int)(mirror.Reflection.Value)); diff --git a/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs b/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs index 90ef85c85..e2f38ad5d 100644 --- a/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs +++ b/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs @@ -222,7 +222,7 @@ private void calculate() var difficultyCalculator = rulesetInstance.CreateDifficultyCalculator(working); - Dictionary attributesCache = new(); + Dictionary attributesCache = new Dictionary(); foreach (var score in leaderboard.Scores) { @@ -236,14 +236,9 @@ private void calculate() var parsedScore = new ProcessorScoreDecoder(working).Parse(scoreInfo); - DifficultyAttributes difficultyAttributes; int modsHash = RulesetHelper.GenerateModsHash(mods, working.BeatmapInfo.Difficulty, ruleset.Value); - if (attributesCache.ContainsKey(modsHash)) - { - difficultyAttributes = attributesCache[modsHash]; - } - else + if (!attributesCache.TryGetValue(modsHash, out var difficultyAttributes)) { difficultyAttributes = difficultyCalculator.Calculate(mods); attributesCache[modsHash] = difficultyAttributes; diff --git a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs index 834deba06..fdb464970 100644 --- a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs +++ b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs @@ -462,7 +462,7 @@ private void calculateProfileFromLazer(string username) List tempScores = []; - Dictionary attributesCache = new(); + Dictionary attributesCache = new Dictionary(); foreach (var score in scoreList) { @@ -471,14 +471,9 @@ private void calculateProfileFromLazer(string username) Schedule(() => loadingLayer.Text.Value = $"Calculating {player.Username}'s scores... {currentScoresCount} / {totalScoresCount}"); - DifficultyAttributes difficultyAttributes; int modsHash = RulesetHelper.GenerateModsHash(score.Mods, working.BeatmapInfo.Difficulty, ruleset.Value); - if (attributesCache.ContainsKey(modsHash)) - { - difficultyAttributes = attributesCache[modsHash]; - } - else + if (!attributesCache.TryGetValue(modsHash, out var difficultyAttributes)) { difficultyAttributes = difficultyCalculator.Calculate(score.Mods); attributesCache[modsHash] = difficultyAttributes; @@ -560,27 +555,27 @@ private List> getRealmScores(RealmAccess realm) Schedule(() => loadingLayer.Text.Value = "Filtering scores..."); realmScores.RemoveAll(x => !currentUser.Contains(x.User.Username) // Wrong username - || x.BeatmapInfo == null // No map for score - || x.Passed == false || x.Rank == ScoreRank.F // Failed score - || x.Ruleset.OnlineID != ruleset.Value.OnlineID // Incorrect ruleset - || settingsMenu.ShouldBeFiltered(x)); // Customisable filters - - List> groupedScores = realmScores.GroupBy(g => g.BeatmapHash) - .Select(s => s.ToList()) - .ToList(); + || x.BeatmapInfo == null // No map for score + || x.Passed == false || x.Rank == ScoreRank.F // Failed score + || x.Ruleset.OnlineID != ruleset.Value.OnlineID // Incorrect ruleset + || settingsMenu.ShouldBeFiltered(x)); // Customisable filters + + List> groupedScores = realmScores.GroupBy(g => g.BeatmapHash).Select(s => s.ToList()).ToList(); + // Simulate scorev1 if enabled if (settingsMenu.IsScorev1OverwritingEnabled) { var rulesetInstance = ruleset.Value.CreateInstance(); - List> filteredScores = new(); + List> filteredScores = new List>(); foreach (var mapScores in groupedScores) { List filteredMapScores = mapScores.Where(s => s.IsLegacyScore) - .GroupBy(x => rulesetInstance.ConvertToLegacyMods(x.Mods)) - .Select(x => x.MaxBy(x => x.LegacyTotalScore)) - .ToList(); + .GroupBy(x => rulesetInstance.ConvertToLegacyMods(x.Mods)) + .Select(g => g.MaxBy(g => g.LegacyTotalScore)) + .ToList(); + filteredMapScores.AddRange(mapScores.Where(s => !s.IsLegacyScore)); filteredScores.Add(mapScores); } From ce15637ded9be9a47d096d3e7a90bb0ac042e83a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 13 Aug 2024 20:51:41 +0300 Subject: [PATCH 04/14] CI fix again --- .../Screens/ProfileScreen.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs index fdb464970..c25f1342e 100644 --- a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs +++ b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs @@ -453,6 +453,7 @@ private void calculateProfileFromLazer(string username) foreach (var scoreList in realmScores) { + string beatmapHash = scoreList[0].BeatmapHash; //get the .osu file from lazer file storage var working = new FlatWorkingBeatmap(Path.Combine(lazerPath, "files", beatmapHash[..1], beatmapHash[..2], beatmapHash)); @@ -471,6 +472,9 @@ private void calculateProfileFromLazer(string username) Schedule(() => loadingLayer.Text.Value = $"Calculating {player.Username}'s scores... {currentScoresCount} / {totalScoresCount}"); + if (score.BeatmapInfo == null) + continue; + int modsHash = RulesetHelper.GenerateModsHash(score.Mods, working.BeatmapInfo.Difficulty, ruleset.Value); if (!attributesCache.TryGetValue(modsHash, out var difficultyAttributes)) @@ -485,8 +489,8 @@ private void calculateProfileFromLazer(string username) currentScoresCount++; - // Sanity check for aspire maps till my slider fix won't get merged - if (difficultyAttributes.StarRating > 14 && score.BeatmapInfo.Status != BeatmapOnlineStatus.Ranked) + // Sanity check for aspire maps till slider fix won't get merged + if (difficultyAttributes.StarRating > 14 && !score.BeatmapInfo.Status.GrantsPerformancePoints()) continue; tempScores.Add(new ProfileScore(score, perfAttributes)); @@ -555,10 +559,10 @@ private List> getRealmScores(RealmAccess realm) Schedule(() => loadingLayer.Text.Value = "Filtering scores..."); realmScores.RemoveAll(x => !currentUser.Contains(x.User.Username) // Wrong username - || x.BeatmapInfo == null // No map for score - || x.Passed == false || x.Rank == ScoreRank.F // Failed score - || x.Ruleset.OnlineID != ruleset.Value.OnlineID // Incorrect ruleset - || settingsMenu.ShouldBeFiltered(x)); // Customisable filters + || x.BeatmapInfo == null // No map for score + || x.Passed == false || x.Rank == ScoreRank.F // Failed score + || x.Ruleset.OnlineID != ruleset.Value.OnlineID // Incorrect ruleset + || settingsMenu.ShouldBeFiltered(x)); // Customisable filters List> groupedScores = realmScores.GroupBy(g => g.BeatmapHash).Select(s => s.ToList()).ToList(); @@ -571,9 +575,9 @@ private List> getRealmScores(RealmAccess realm) foreach (var mapScores in groupedScores) { - List filteredMapScores = mapScores.Where(s => s.IsLegacyScore) + List filteredMapScores = mapScores.Where(x => x.IsLegacyScore) .GroupBy(x => rulesetInstance.ConvertToLegacyMods(x.Mods)) - .Select(g => g.MaxBy(g => g.LegacyTotalScore)) + .Select(x => x.MaxBy(x => x.LegacyTotalScore)) .ToList(); filteredMapScores.AddRange(mapScores.Where(s => !s.IsLegacyScore)); From ed1d7f6372b9f7392553dfc3884ffa00711ac32d Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 13 Aug 2024 20:54:55 +0300 Subject: [PATCH 05/14] Update ProfileScreen.cs --- PerformanceCalculatorGUI/Screens/ProfileScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs index c25f1342e..0921e633e 100644 --- a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs +++ b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs @@ -453,8 +453,8 @@ private void calculateProfileFromLazer(string username) foreach (var scoreList in realmScores) { - string beatmapHash = scoreList[0].BeatmapHash; + //get the .osu file from lazer file storage var working = new FlatWorkingBeatmap(Path.Combine(lazerPath, "files", beatmapHash[..1], beatmapHash[..2], beatmapHash)); From eef465b9678dd7b83b3986d7e7a721caad1e97ed Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 14 Aug 2024 15:45:33 +0300 Subject: [PATCH 06/14] updated the text and disabled RX filtering --- .../Components/LazerCalculationSettings.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs index 51bec38ea..7b8b2d7de 100644 --- a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs +++ b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs @@ -44,7 +44,7 @@ public LazerCalculationSettings() public bool ShouldBeFiltered(ScoreInfo score) { - if (score.Mods.Any(h => h is OsuModRelax || h is OsuModAutopilot)) + if (score.Mods.Any(h => h is OsuModAutopilot)) return true; if (score.BeatmapInfo == null) @@ -110,22 +110,22 @@ private void load() { new OsuCheckbox { - LabelText = "Calculate Ranked Maps (ranked & approved)", + LabelText = "Calculate Ranked Maps", Current = { BindTarget = bindables[0] } }, new OsuCheckbox { - LabelText = "Calculate Unranked Maps (everything else)", + LabelText = "Calculate Unranked Maps", Current = { BindTarget = bindables[1] } }, new OsuCheckbox { - LabelText = "Calculate Unsubmitted Scores (on local difficulties for example)", + LabelText = "Calculate Unsubmitted Scores, such as scores set on local difficulties", Current = { BindTarget = bindables[2] } }, new OsuCheckbox { - LabelText = "Calculate Unranked Mods (RX and AP are excluded regardless)", + LabelText = "Calculate Unranked Mods, Autopilot is excluded regardless", Current = { BindTarget = bindables[3] } }, new OsuCheckbox From dd2123e150e7efbc50a3b20a9a0da8c0730d0a1f Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 23 Aug 2024 02:11:58 +0300 Subject: [PATCH 07/14] fixed scorev1 simulation not working --- PerformanceCalculatorGUI/Screens/ProfileScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs index 0921e633e..1fedc46ea 100644 --- a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs +++ b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs @@ -581,7 +581,7 @@ private List> getRealmScores(RealmAccess realm) .ToList(); filteredMapScores.AddRange(mapScores.Where(s => !s.IsLegacyScore)); - filteredScores.Add(mapScores); + filteredScores.Add(filteredMapScores); } groupedScores = filteredScores; From 9acd859d0c0e42e561bd52a00d3e1c22fa49f423 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 28 Aug 2024 10:37:14 +0300 Subject: [PATCH 08/14] added HR check --- PerformanceCalculatorGUI/RulesetHelper.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index 75c4d9a2f..053c2e7df 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -118,9 +118,13 @@ public static int GenerateModsHash(Mod[] mods, BeatmapDifficulty difficulty, Rul byte mirrorHash = 0; - if (mods.FirstOrDefault(m => m is OsuModMirror) is OsuModMirror mirror) + if (mods.Any(m => m is OsuModHardRock)) { - mirrorHash = (byte)(1 + (int)(mirror.Reflection.Value)); + mirrorHash = 1 + (int)OsuModMirror.MirrorType.Vertical; + } + else if (mods.FirstOrDefault(m => m is OsuModMirror) is OsuModMirror mirror) + { + mirrorHash = (byte)(1 + (int)mirror.Reflection.Value); } hash = HashCode.Combine(rate, d.CircleSize, d.OverallDifficulty, isSliderAccuracy, flashlightHash, mirrorHash); From 5f912bfdef532d5c6900a082258780d9a5c4f63a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 31 Aug 2024 16:08:29 +0300 Subject: [PATCH 09/14] removed arbitrary filtering --- .../Components/LazerCalculationSettings.cs | 3 --- PerformanceCalculatorGUI/Screens/ProfileScreen.cs | 4 ---- 2 files changed, 7 deletions(-) diff --git a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs index 7b8b2d7de..67d65cc5b 100644 --- a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs +++ b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs @@ -44,9 +44,6 @@ public LazerCalculationSettings() public bool ShouldBeFiltered(ScoreInfo score) { - if (score.Mods.Any(h => h is OsuModAutopilot)) - return true; - if (score.BeatmapInfo == null) return true; diff --git a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs index 1fedc46ea..d7e26f635 100644 --- a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs +++ b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs @@ -489,10 +489,6 @@ private void calculateProfileFromLazer(string username) currentScoresCount++; - // Sanity check for aspire maps till slider fix won't get merged - if (difficultyAttributes.StarRating > 14 && !score.BeatmapInfo.Status.GrantsPerformancePoints()) - continue; - tempScores.Add(new ProfileScore(score, perfAttributes)); } From a2b4f3b27e0f93ffdefb3644f59db666b881386d Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 1 Sep 2024 01:41:19 +0300 Subject: [PATCH 10/14] removed bindables array --- .../Components/LazerCalculationSettings.cs | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs index 67d65cc5b..ca85a3944 100644 --- a/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs +++ b/PerformanceCalculatorGUI/Components/LazerCalculationSettings.cs @@ -23,15 +23,15 @@ namespace PerformanceCalculatorGUI.Components { public partial class LazerCalculationSettings : ToolbarButton, IHasPopover { - private readonly Bindable calculateRankedMaps = new Bindable(true); - private readonly Bindable calculateUnrankedMaps = new Bindable(false); + public readonly Bindable CalculateRankedMaps = new Bindable(true); + public readonly Bindable CalculateUnrankedMaps = new Bindable(false); - private readonly Bindable calculateUnsubmittedScores = new Bindable(true); - private readonly Bindable calculateUnrankedMods = new Bindable(true); + public readonly Bindable CalculateUnsubmittedScores = new Bindable(true); + public readonly Bindable CalculateUnrankedMods = new Bindable(true); - private readonly Bindable enableScorev1Overwrite = new Bindable(false); + public readonly Bindable EnableScorev1Overwrite = new Bindable(false); - public bool IsScorev1OverwritingEnabled => enableScorev1Overwrite.Value; + public bool IsScorev1OverwritingEnabled => EnableScorev1Overwrite.Value; protected override Anchor TooltipAnchor => Anchor.TopRight; @@ -47,20 +47,20 @@ public bool ShouldBeFiltered(ScoreInfo score) if (score.BeatmapInfo == null) return true; - if (!calculateRankedMaps.Value && score.BeatmapInfo.Status.GrantsPerformancePoints()) + if (!CalculateRankedMaps.Value && score.BeatmapInfo.Status.GrantsPerformancePoints()) return true; - if (!calculateUnrankedMaps.Value && !score.BeatmapInfo.Status.GrantsPerformancePoints()) + if (!CalculateUnrankedMaps.Value && !score.BeatmapInfo.Status.GrantsPerformancePoints()) return true; - if (!calculateUnrankedMods.Value) + if (!CalculateUnrankedMods.Value) { // Check for legacy score because CL is unranked if (!score.Mods.All(m => m.Ranked || (score.IsLegacyScore && m is OsuModClassic))) return true; } - if (!calculateUnsubmittedScores.Value) + if (!CalculateUnsubmittedScores.Value) { if (score.OnlineID <= 0 && score.LegacyOnlineID <= 0) return true; @@ -69,8 +69,7 @@ public bool ShouldBeFiltered(ScoreInfo score) return false; } - public Popover GetPopover() => new LazerCalculationSettingsPopover( - new[] { calculateRankedMaps, calculateUnrankedMaps, calculateUnsubmittedScores, calculateUnrankedMods, enableScorev1Overwrite }); + public Popover GetPopover() => new LazerCalculationSettingsPopover(this); protected override bool OnClick(ClickEvent e) { @@ -81,11 +80,11 @@ protected override bool OnClick(ClickEvent e) public partial class LazerCalculationSettingsPopover : OsuPopover { - private readonly Bindable[] bindables; + private readonly LazerCalculationSettings parent; - public LazerCalculationSettingsPopover(Bindable[] bindables) + public LazerCalculationSettingsPopover(LazerCalculationSettings parent) { - this.bindables = bindables; + this.parent = parent; } [BackgroundDependencyLoader] @@ -108,27 +107,27 @@ private void load() new OsuCheckbox { LabelText = "Calculate Ranked Maps", - Current = { BindTarget = bindables[0] } + Current = { BindTarget = parent.CalculateRankedMaps } }, new OsuCheckbox { LabelText = "Calculate Unranked Maps", - Current = { BindTarget = bindables[1] } + Current = { BindTarget = parent.CalculateUnrankedMaps } }, new OsuCheckbox { LabelText = "Calculate Unsubmitted Scores, such as scores set on local difficulties", - Current = { BindTarget = bindables[2] } + Current = { BindTarget = parent.CalculateUnsubmittedScores } }, new OsuCheckbox { LabelText = "Calculate Unranked Mods, Autopilot is excluded regardless", - Current = { BindTarget = bindables[3] } + Current = { BindTarget = parent.CalculateUnsubmittedScores } }, new OsuCheckbox { LabelText = "Enable Scorev1 score overwrite for legacy scores", - Current = { BindTarget = bindables[4] } + Current = { BindTarget = parent.EnableScorev1Overwrite } }, } } From 6d082ac202bb8dc0ac004b3dc8a0cc3b490a9626 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 1 Sep 2024 01:42:17 +0300 Subject: [PATCH 11/14] changed error to unknown --- PerformanceCalculatorGUI/Components/ProfileScore.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PerformanceCalculatorGUI/Components/ProfileScore.cs b/PerformanceCalculatorGUI/Components/ProfileScore.cs index 7c8813581..0b42859c2 100644 --- a/PerformanceCalculatorGUI/Components/ProfileScore.cs +++ b/PerformanceCalculatorGUI/Components/ProfileScore.cs @@ -54,15 +54,15 @@ private static SoloScoreInfo toSoloScoreInfo(ScoreInfo score) { APIBeatmapSet dummySet = new APIBeatmapSet { - Title = score.BeatmapInfo?.Metadata.Title ?? "Error Title", - TitleUnicode = score.BeatmapInfo?.Metadata.TitleUnicode ?? "Error Title", - Artist = score.BeatmapInfo?.Metadata.Artist ?? "Error Artist", - ArtistUnicode = score.BeatmapInfo?.Metadata.ArtistUnicode ?? "Error Artist", + Title = score.BeatmapInfo?.Metadata.Title ?? "Unknown Title", + TitleUnicode = score.BeatmapInfo?.Metadata.TitleUnicode ?? "Unknown Title", + Artist = score.BeatmapInfo?.Metadata.Artist ?? "Unknown Artist", + ArtistUnicode = score.BeatmapInfo?.Metadata.ArtistUnicode ?? "Unknown Artist", }; APIBeatmap dummyBeatmap = new APIBeatmap { OnlineID = score.BeatmapInfo?.OnlineID ?? 0, - DifficultyName = score.BeatmapInfo?.DifficultyName ?? "Error Difficulty", + DifficultyName = score.BeatmapInfo?.DifficultyName ?? "Unknown Difficulty", }; SoloScoreInfo soloScoreInfo = new SoloScoreInfo { From 417c6deb2b4802e4834d6cd7fa9885748d333bf3 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 1 Sep 2024 20:07:03 +0300 Subject: [PATCH 12/14] changed hashing --- PerformanceCalculatorGUI/RulesetHelper.cs | 98 ++++--------------- .../Screens/BeatmapLeaderboardScreen.cs | 11 +-- .../Screens/ProfileScreen.cs | 16 ++- 3 files changed, 37 insertions(+), 88 deletions(-) diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index 053c2e7df..313fddf60 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -13,10 +13,8 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko.Objects; @@ -28,10 +26,9 @@ namespace PerformanceCalculatorGUI public static class RulesetHelper { /// - /// Transforms a given combination into one which is applicable to legacy scores. - /// This is used to match osu!stable/osu!web calculations for the time being, until such a point that these mods do get considered. + /// Creates hashset of types for each of that's affecting difficulty /// - public static Mod[] ConvertToLegacyDifficultyAdjustmentMods(Ruleset ruleset, Mod[] mods) + public static HashSet GetDifficultyAdjustingModsHashSet(Ruleset ruleset) { var beatmap = new EmptyWorkingBeatmap { @@ -44,15 +41,30 @@ public static Mod[] ConvertToLegacyDifficultyAdjustmentMods(Ruleset ruleset, Mod var allMods = ruleset.CreateAllMods().ToArray(); - var allowedMods = ModUtils.FlattenMods( + var difficultyAdjustingMods = ModUtils.FlattenMods( ruleset.CreateDifficultyCalculator(beatmap).CreateDifficultyAdjustmentModCombinations()) .Select(m => m.GetType()) .Distinct() .ToHashSet(); - // Special case to allow either DT or NC. - if (allowedMods.Any(type => type.IsSubclassOf(typeof(ModDoubleTime))) && mods.Any(m => m is ModNightcore)) - allowedMods.Add(allMods.Single(m => m is ModNightcore).GetType()); + // Explicitly add NC and DC as they're not returned in CreateDifficultyAdjustmentModCombinations for some reason + if (difficultyAdjustingMods.Any(type => type.IsSubclassOf(typeof(ModDoubleTime)))) + difficultyAdjustingMods.Add(allMods.Single(m => m is ModNightcore).GetType()); + + if (difficultyAdjustingMods.Any(type => type.IsSubclassOf(typeof(ModHalfTime)))) + difficultyAdjustingMods.Add(allMods.Single(m => m is ModDaycore).GetType()); + + return difficultyAdjustingMods; + } + + /// + /// Transforms a given combination into one which is applicable to legacy scores. + /// This is used to match osu!stable/osu!web calculations for the time being, until such a point that these mods do get considered. + /// + public static Mod[] ConvertToLegacyDifficultyAdjustmentMods(Ruleset ruleset, Mod[] mods) + { + var allMods = ruleset.CreateAllMods().ToArray(); + var allowedMods = GetDifficultyAdjustingModsHashSet(ruleset); var result = new List(); @@ -89,74 +101,6 @@ public static Ruleset GetRulesetFromLegacyID(int id) }; } - /// - /// Generates the unique hash of mods combo that affect difficulty calculation - /// Needs to be updated if list of difficulty adjusting mods changes - /// - public static int GenerateModsHash(Mod[] mods, BeatmapDifficulty difficulty, RulesetInfo ruleset) - { - // Rate changing mods - double rate = ModUtils.CalculateRateWithMods(mods); - - int hash = 0; - - if (ruleset.OnlineID == 0) // For osu we have many different things - { - BeatmapDifficulty d = new BeatmapDifficulty(difficulty); - - foreach (var mod in mods.OfType()) - mod.ApplyToDifficulty(d); - - bool isSliderAccuracy = mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value); - - byte flashlightHash = 0; - - if (mods.Any(h => h is OsuModFlashlight)) - { - flashlightHash = (byte)(mods.Any(h => h is OsuModHidden) ? 2 : 1); - } - - byte mirrorHash = 0; - - if (mods.Any(m => m is OsuModHardRock)) - { - mirrorHash = 1 + (int)OsuModMirror.MirrorType.Vertical; - } - else if (mods.FirstOrDefault(m => m is OsuModMirror) is OsuModMirror mirror) - { - mirrorHash = (byte)(1 + (int)mirror.Reflection.Value); - } - - hash = HashCode.Combine(rate, d.CircleSize, d.OverallDifficulty, isSliderAccuracy, flashlightHash, mirrorHash); - } - else if (ruleset.OnlineID == 1) // For taiko we only have rate - { - hash = rate.GetHashCode(); - } - else if (ruleset.OnlineID == 2) // For catch we have rate and CS - { - BeatmapDifficulty d = new BeatmapDifficulty(difficulty); - - foreach (var mod in mods.OfType()) - mod.ApplyToDifficulty(d); - - hash = HashCode.Combine(rate, d.CircleSize); - } - else if (ruleset.OnlineID == 3) // Mania is using rate, and keys data for converts - { - int keyCount = 0; - - if (mods.FirstOrDefault(h => h is ManiaKeyMod) is ManiaKeyMod mod) - keyCount = mod.KeyCount; - - bool isDualStages = mods.Any(h => h is ManiaModDualStages); - - hash = HashCode.Combine(rate, keyCount, isDualStages); - } - - return hash; - } - public static int AdjustManiaScore(int score, IReadOnlyList mods) { if (score != 1000000) return score; diff --git a/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs b/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs index e2f38ad5d..b5bb5a4f6 100644 --- a/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs +++ b/PerformanceCalculatorGUI/Screens/BeatmapLeaderboardScreen.cs @@ -19,7 +19,6 @@ using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using PerformanceCalculatorGUI.Components; @@ -222,8 +221,6 @@ private void calculate() var difficultyCalculator = rulesetInstance.CreateDifficultyCalculator(working); - Dictionary attributesCache = new Dictionary(); - foreach (var score in leaderboard.Scores) { if (token.IsCancellationRequested) @@ -236,13 +233,7 @@ private void calculate() var parsedScore = new ProcessorScoreDecoder(working).Parse(scoreInfo); - int modsHash = RulesetHelper.GenerateModsHash(mods, working.BeatmapInfo.Difficulty, ruleset.Value); - - if (!attributesCache.TryGetValue(modsHash, out var difficultyAttributes)) - { - difficultyAttributes = difficultyCalculator.Calculate(mods); - attributesCache[modsHash] = difficultyAttributes; - } + var difficultyAttributes = difficultyCalculator.Calculate(mods); var performanceCalculator = rulesetInstance.CreatePerformanceCalculator(); diff --git a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs index d7e26f635..c49c177c7 100644 --- a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs +++ b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs @@ -451,6 +451,8 @@ private void calculateProfileFromLazer(string username) int currentScoresCount = 0; var totalScoresCount = realmScores.Sum(childList => childList.Count); + var relevantMods = RulesetHelper.GetDifficultyAdjustingModsHashSet(rulesetInstance); + foreach (var scoreList in realmScores) { string beatmapHash = scoreList[0].BeatmapHash; @@ -475,7 +477,7 @@ private void calculateProfileFromLazer(string username) if (score.BeatmapInfo == null) continue; - int modsHash = RulesetHelper.GenerateModsHash(score.Mods, working.BeatmapInfo.Difficulty, ruleset.Value); + int modsHash = generateModsHash(score.Mods, relevantMods); if (!attributesCache.TryGetValue(modsHash, out var difficultyAttributes)) { @@ -547,6 +549,18 @@ private void calculateProfileFromLazer(string username) }, token); } + private int generateModsHash(IReadOnlyList mods, HashSet relevantMods) + { + HashCode hash = new HashCode(); + + var filteredMods = mods.Where(m => relevantMods.Contains(m.GetType())); + + foreach (var mod in filteredMods) + hash.Add(mod); + + return hash.ToHashCode(); + } + private List> getRealmScores(RealmAccess realm) { Schedule(() => loadingLayer.Text.Value = "Extracting user scores..."); From 2b28193cd65d189da335f344cd1a2eb319abfa2b Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 2 Sep 2024 22:24:52 +0300 Subject: [PATCH 13/14] renamed `currentUser` --- PerformanceCalculatorGUI/Screens/ProfileScreen.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs index c49c177c7..01e1a7b76 100644 --- a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs +++ b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs @@ -54,7 +54,7 @@ public partial class ProfileScreen : PerformanceCalculatorScreen private StatefulButton calculationButtonLocal; private LazerCalculationSettings settingsMenu; - private string[] currentUser; + private string[] currentUserNicknames; private CancellationTokenSource calculationCancellatonToken; @@ -280,7 +280,7 @@ private void calculateProfileFromServer(string username) var player = await apiManager.GetJsonFromApi($"users/{username}/{ruleset.Value.ShortName}"); - currentUser = [player.Username]; + currentUserNicknames = [player.Username]; Schedule(() => { @@ -413,7 +413,7 @@ private void calculateProfileFromLazer(string username) var player = await apiManager.GetJsonFromApi($"users/{username}/{ruleset.Value.ShortName}"); - currentUser = [player.Username, .. player.PreviousUsernames, player.Id.ToString()]; + currentUserNicknames = [player.Username, .. player.PreviousUsernames, player.Id.ToString()]; Schedule(() => { @@ -568,7 +568,7 @@ private List> getRealmScores(RealmAccess realm) Schedule(() => loadingLayer.Text.Value = "Filtering scores..."); - realmScores.RemoveAll(x => !currentUser.Contains(x.User.Username) // Wrong username + realmScores.RemoveAll(x => !currentUserNicknames.Contains(x.User.Username) // Wrong username || x.BeatmapInfo == null // No map for score || x.Passed == false || x.Rank == ScoreRank.F // Failed score || x.Ruleset.OnlineID != ruleset.Value.OnlineID // Incorrect ruleset From dc560e3b955044f8bc4a8f15ca25bde848a019e1 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 2 Sep 2024 23:59:30 +0300 Subject: [PATCH 14/14] added functionality to calculate offline profile --- .../Screens/ProfileScreen.cs | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs index 01e1a7b76..b5a4d028e 100644 --- a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs +++ b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs @@ -27,6 +27,7 @@ using PerformanceCalculatorGUI.Configuration; using System.IO; using osu.Framework.Platform; +using osu.Game.Screens.Play; namespace PerformanceCalculatorGUI.Screens { @@ -411,9 +412,24 @@ private void calculateProfileFromLazer(string username) { Schedule(() => loadingLayer.Text.Value = "Getting user data..."); - var player = await apiManager.GetJsonFromApi($"users/{username}/{ruleset.Value.ShortName}"); + APIUser player = null; + + try + { + player = await apiManager.GetJsonFromApi($"users/{username}/{ruleset.Value.ShortName}"); + + currentUserNicknames = [player.Username, .. player.PreviousUsernames, player.Id.ToString()]; - currentUserNicknames = [player.Username, .. player.PreviousUsernames, player.Id.ToString()]; + } catch { + notificationDisplay.Display(new Notification("Unable to find player on the servers, using local name...")); + + player = new APIUser + { + Username = username + }; + + currentUserNicknames = [username]; + } Schedule(() => { @@ -430,11 +446,8 @@ private void calculateProfileFromLazer(string username) if (token.IsCancellationRequested) return; - var plays = new List(); - var rulesetInstance = ruleset.Value.CreateInstance(); - var lazerPath = configManager.GetBindable(Settings.LazerFolderPath).Value; if (lazerPath == string.Empty) @@ -444,6 +457,7 @@ private void calculateProfileFromLazer(string username) } var storage = gameHost.GetStorage(lazerPath); + var realmAccess = new RealmAccess(storage, @"client.realm"); var realmScores = getRealmScores(realmAccess); @@ -472,7 +486,7 @@ private void calculateProfileFromLazer(string username) if (token.IsCancellationRequested) return; - Schedule(() => loadingLayer.Text.Value = $"Calculating {player.Username}'s scores... {currentScoresCount} / {totalScoresCount}"); + Schedule(() => loadingLayer.Text.Value = $"Calculating {username}'s scores... {currentScoresCount} / {totalScoresCount}"); if (score.BeatmapInfo == null) continue; @@ -568,7 +582,7 @@ private List> getRealmScores(RealmAccess realm) Schedule(() => loadingLayer.Text.Value = "Filtering scores..."); - realmScores.RemoveAll(x => !currentUserNicknames.Contains(x.User.Username) // Wrong username + realmScores.RemoveAll(x => !currentUserNicknames.Any(nickname => nickname.Equals(x.User.Username, StringComparison.OrdinalIgnoreCase)) // Wrong username || x.BeatmapInfo == null // No map for score || x.Passed == false || x.Rank == ScoreRank.F // Failed score || x.Ruleset.OnlineID != ruleset.Value.OnlineID // Incorrect ruleset