From 207567ed1598d21172d68775add76a4b4ef6ac71 Mon Sep 17 00:00:00 2001 From: KermitNuggies Date: Sat, 14 Feb 2026 19:30:08 +1300 Subject: [PATCH 1/2] Swap extended score from using SoloScoreInfo to IScoreInfo (and update all uses) --- .../Components/ExtendedProfileScore.cs | 86 ++++++++++++------- .../Screens/Collections/ScoreContainer.cs | 6 +- .../Screens/CollectionsScreen.cs | 8 +- .../Screens/ProfileScreen.cs | 10 +-- 4 files changed, 66 insertions(+), 44 deletions(-) diff --git a/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs b/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs index 38e553fbb3..e5d08ce551 100644 --- a/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs +++ b/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs @@ -18,14 +18,17 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; 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.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Users.Drawables; using osu.Game.Utils; using osuTK; @@ -36,7 +39,7 @@ namespace PerformanceCalculatorGUI.Components { public class ExtendedScore { - public SoloScoreInfo SoloScore { get; } + public IScoreInfo Score { get; } public double? LivePP { get; } public Bindable Position { get; } = new Bindable(); @@ -45,13 +48,32 @@ public class ExtendedScore public PerformanceAttributes? PerformanceAttributes { get; } public DifficultyAttributes DifficultyAttributes { get; } - public ExtendedScore(SoloScoreInfo score, DifficultyAttributes difficultyAttributes, PerformanceAttributes? performanceAttributes) + public ExtendedScore(IScoreInfo score, DifficultyAttributes difficultyAttributes, PerformanceAttributes? performanceAttributes) { - SoloScore = score; + Score = score; PerformanceAttributes = performanceAttributes; DifficultyAttributes = difficultyAttributes; LivePP = score.PP; } + + public Dictionary? Statistics() + { + if (Score is SoloScoreInfo soloScore) + return soloScore.Statistics; + if (Score is ScoreInfo scoreInfo) + return scoreInfo.Statistics; + return null; + } + public APIMod[]? Mods() + { + if (Score is ScoreInfo scoreInfo) + return scoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); + if (Score is SoloScoreInfo soloScoreInfo) + { + return soloScoreInfo.Mods; + } + return null; + } } public partial class ExtendedProfileItemContainer : ProfileItemContainer @@ -88,7 +110,7 @@ public partial class ExtendedProfileScore : CompositeDrawable private const float performance_background_shear = 0.45f; - public readonly ExtendedScore Score; + public readonly ExtendedScore ExtScore; public readonly bool ShowAvatar; @@ -102,7 +124,7 @@ public partial class ExtendedProfileScore : CompositeDrawable public ExtendedProfileScore(ExtendedScore score, bool showAvatar = false) { - Score = score; + ExtScore = score; ShowAvatar = showAvatar; RelativeSizeAxes = Axes.X; @@ -114,27 +136,27 @@ private void load(GameHost host, RulesetStore rulesets) { int avatarPadding = ShowAvatar ? avatar_size : 0; int rankDifferenceWidth = ShowAvatar ? 8 : rank_difference_width; - var scoreRuleset = rulesets.GetRuleset(Score.SoloScore.RulesetID)?.CreateInstance() ?? throw new InvalidOperationException(); + var scoreRuleset = rulesets.GetRuleset(ExtScore.Score.Ruleset.OnlineID)?.CreateInstance() ?? throw new InvalidOperationException(); AddInternal(new ExtendedProfileItemContainer { OnHoverAction = () => { - positionChangeText.Text = $"#{Score.Position.Value}"; + positionChangeText.Text = $"#{ExtScore.Position.Value}"; }, OnUnhoverAction = () => { - positionChangeText.Text = $"{Score.PositionChange.Value:+0;-0;-}"; + positionChangeText.Text = $"{ExtScore.PositionChange.Value:+0;-0;-}"; }, Children = new[] { ShowAvatar - ? new ClickableAvatar(Score.SoloScore.User, true) + ? new ClickableAvatar((APIUser)ExtScore.Score.User, true) { Masking = true, CornerRadius = ExtendedLabelledTextBox.CORNER_RADIUS, Size = new Vector2(avatar_size), - Action = () => { host.OpenUrlExternally($"https://osu.ppy.sh/users/{Score.SoloScore.User?.Id}"); } + Action = () => { host.OpenUrlExternally($"https://osu.ppy.sh/users/{ExtScore.Score.User?.OnlineID}"); } } : Empty(), new Container @@ -151,7 +173,7 @@ private void load(GameHost host, RulesetStore rulesets) Anchor = Anchor.Centre, Origin = Anchor.Centre, Colour = colourProvider.Light1, - Text = $"{Score.PositionChange.Value:+0;-0;-}" + Text = $"{ExtScore.PositionChange.Value:+0;-0;-}" } }, new Container @@ -180,13 +202,13 @@ private void load(GameHost host, RulesetStore rulesets) Padding = new MarginPadding { Top = 2 }, Children = new Drawable[] { - new UpdateableRank(Score.SoloScore.Rank) + new UpdateableRank(ExtScore.Score.Rank) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Size = new Vector2(40, 12), }, - new TinyStarRatingDisplay(Score.DifficultyAttributes) + new TinyStarRatingDisplay(ExtScore.DifficultyAttributes) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -202,7 +224,7 @@ private void load(GameHost host, RulesetStore rulesets) Spacing = new Vector2(0, 0.5f), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Score.SoloScore.Beatmap), + new ScoreBeatmapMetadataContainer(ExtScore.Score.Beatmap), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -212,11 +234,11 @@ private void load(GameHost host, RulesetStore rulesets) { new OsuSpriteText { - Text = $"{Score.SoloScore.Beatmap?.DifficultyName}", + Text = $"{ExtScore.Score.Beatmap?.DifficultyName}", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, - new DrawableDate(Score.SoloScore.EndedAt, 12) + new DrawableDate(ExtScore.Score.Date, 12) { Colour = colourProvider.Foreground1 } @@ -268,7 +290,7 @@ private void load(GameHost host, RulesetStore rulesets) { new OsuSpriteText { - Text = Score.SoloScore.Accuracy.FormatAccuracy(), + Text = ExtScore.Score.Accuracy.FormatAccuracy(), Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true), Colour = colours.Yellow, Anchor = Anchor.TopCentre, @@ -285,7 +307,7 @@ private void load(GameHost host, RulesetStore rulesets) formatCombo(), new OsuSpriteText { - Text = $"{{ {formatStatistics(Score.SoloScore.Statistics, scoreRuleset)} }}", + Text = $"{{ {formatStatistics(ExtScore.Statistics(), scoreRuleset)} }}", Font = OsuFont.GetFont(size: small_text_font_size, weight: FontWeight.Regular), Colour = colourProvider.Light2, Anchor = Anchor.TopCentre, @@ -310,7 +332,7 @@ private void load(GameHost host, RulesetStore rulesets) Child = new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = Score.LivePP != null ? $"{Score.LivePP:0}pp" : "- pp" + Text = ExtScore.LivePP != null ? $"{ExtScore.LivePP:0}pp" : "- pp" }, }, new OsuSpriteText @@ -332,7 +354,7 @@ private void load(GameHost host, RulesetStore rulesets) Origin = Anchor.CentreRight, Direction = FillDirection.Horizontal, Spacing = new Vector2(2), - Children = Score.SoloScore.Mods.Select(mod => new ModIcon(mod.ToMod(scoreRuleset)) + Children = ExtScore.Mods().Select(mod => new ModIcon(mod.ToMod(scoreRuleset)) { Scale = new Vector2(0.35f) }).ToList(), @@ -372,27 +394,27 @@ private void load(GameHost host, RulesetStore rulesets) Shear = new Vector2(performance_background_shear, 0), EdgeSmoothness = new Vector2(2, 0), }, - new ScorePerformanceContainer(Score) + new ScorePerformanceContainer(ExtScore) } } } }); - Score.PositionChange.BindValueChanged(v => { positionChangeText.Text = $"{v.NewValue:+0;-0;-}"; }); + ExtScore.PositionChange.BindValueChanged(v => { positionChangeText.Text = $"{v.NewValue:+0;-0;-}"; }); } private OsuSpriteText formatCombo() { - bool isFullCombo = Score.SoloScore.MaxCombo == Score.DifficultyAttributes.MaxCombo; + bool isFullCombo = ExtScore.Score.MaxCombo == ExtScore.DifficultyAttributes.MaxCombo; return new ExtendedOsuSpriteText { - Text = $"{Score.SoloScore.MaxCombo}x", + Text = $"{ExtScore.Score.MaxCombo}x", Font = OsuFont.GetFont(size: small_text_font_size, weight: FontWeight.Regular), Colour = isFullCombo ? colours.GreenLight : colourProvider.Light2, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - TooltipContent = $"{Score.SoloScore.MaxCombo} / {Score.DifficultyAttributes.MaxCombo}x" + TooltipContent = $"{ExtScore.Score.MaxCombo} / {ExtScore.DifficultyAttributes.MaxCombo}x" }; } @@ -471,14 +493,14 @@ private void load(GameHost host) private partial class ScorePerformanceContainer : OsuHoverContainer { - private readonly ExtendedScore score; + private readonly ExtendedScore extScore; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; public ScorePerformanceContainer(ExtendedScore score) { - this.score = score; + this.extScore = score; RelativeSizeAxes = Axes.Both; Padding = new MarginPadding { @@ -493,7 +515,7 @@ private void load(PerformanceCalculatorSceneManager sceneManager) { Action = () => { - sceneManager.SwitchToSimulate(score.SoloScore.BeatmapID, score.SoloScore.ID); + sceneManager.SwitchToSimulate(extScore.Score.Beatmap.OnlineID, (ulong?)extScore.Score.OnlineID); }; Child = new FillFlowContainer @@ -507,16 +529,16 @@ private void load(PerformanceCalculatorSceneManager sceneManager) new ExtendedOsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = $"{score.PerformanceAttributes?.Total:0}pp", + Text = $"{extScore.PerformanceAttributes?.Total:0}pp", Colour = colourProvider.Highlight1, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - TooltipContent = $"{AttributeConversion.ToReadableString(score.PerformanceAttributes)}" + TooltipContent = $"{AttributeConversion.ToReadableString(extScore.PerformanceAttributes)}" }, new OsuSpriteText { Font = OsuFont.GetFont(size: small_text_font_size), - Text = $"{score.PerformanceAttributes?.Total - score.LivePP:+0.0;-0.0;-}", + Text = $"{extScore.PerformanceAttributes?.Total - extScore.LivePP:+0.0;-0.0;-}", Colour = getPpDifferenceColor(), Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre @@ -527,7 +549,7 @@ private void load(PerformanceCalculatorSceneManager sceneManager) private Color4 getPpDifferenceColor() { - double difference = score.PerformanceAttributes?.Total - score.LivePP ?? 0; + double difference = extScore.PerformanceAttributes?.Total - extScore.LivePP ?? 0; var baseColor = colourProvider.Light1; return difference switch diff --git a/PerformanceCalculatorGUI/Screens/Collections/ScoreContainer.cs b/PerformanceCalculatorGUI/Screens/Collections/ScoreContainer.cs index 5c4f8c4626..832c71657b 100644 --- a/PerformanceCalculatorGUI/Screens/Collections/ScoreContainer.cs +++ b/PerformanceCalculatorGUI/Screens/Collections/ScoreContainer.cs @@ -12,7 +12,7 @@ namespace PerformanceCalculatorGUI.Screens.Collections { public partial class ScoreContainer : Container { - public ExtendedScore Score { get; } + public ExtendedScore ExtScore { get; } private readonly IconButton deleteButton; @@ -25,7 +25,7 @@ public ScoreContainer(ExtendedScore score) RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Score = score; + ExtScore = score; Child = new GridContainer { RelativeSizeAxes = Axes.X, @@ -43,7 +43,7 @@ public ScoreContainer(ExtendedScore score) Icon = FontAwesome.Regular.TrashAlt, Action = () => { - OnDelete?.Invoke((long)score.SoloScore.ID!); + OnDelete?.Invoke(ExtScore.Score.OnlineID!); } }, new ExtendedProfileScore(score, true) diff --git a/PerformanceCalculatorGUI/Screens/CollectionsScreen.cs b/PerformanceCalculatorGUI/Screens/CollectionsScreen.cs index e59ec8476d..1253094ac1 100644 --- a/PerformanceCalculatorGUI/Screens/CollectionsScreen.cs +++ b/PerformanceCalculatorGUI/Screens/CollectionsScreen.cs @@ -388,7 +388,7 @@ private void updateSorting(CollectionSortCriteria sortCriteria) { for (int i = 0; i < scoresList.Count; i++) { - scoresList.SetLayoutPosition(scoresList[i], Array.IndexOf(currentCollection.Value!.Scores, scoresList[i].Score.SoloScore.ID)); + scoresList.SetLayoutPosition(scoresList[i], Array.IndexOf(currentCollection.Value!.Scores, scoresList[i].ExtScore.Score.OnlineID)); } return; @@ -399,15 +399,15 @@ private void updateSorting(CollectionSortCriteria sortCriteria) switch (sortCriteria) { case CollectionSortCriteria.Live: - sortedScores = scoresList.Children.OrderByDescending(x => x.Score.LivePP).ToArray(); + sortedScores = scoresList.Children.OrderByDescending(x => x.ExtScore.LivePP).ToArray(); break; case CollectionSortCriteria.Local: - sortedScores = scoresList.Children.OrderByDescending(x => x.Score.PerformanceAttributes?.Total).ToArray(); + sortedScores = scoresList.Children.OrderByDescending(x => x.ExtScore.PerformanceAttributes?.Total).ToArray(); break; case CollectionSortCriteria.Difference: - sortedScores = scoresList.Children.OrderByDescending(x => x.Score.PerformanceAttributes?.Total - x.Score.LivePP).ToArray(); + sortedScores = scoresList.Children.OrderByDescending(x => x.ExtScore.PerformanceAttributes?.Total - x.ExtScore.LivePP).ToArray(); break; default: diff --git a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs index 3fda177589..e48f220d81 100644 --- a/PerformanceCalculatorGUI/Screens/ProfileScreen.cs +++ b/PerformanceCalculatorGUI/Screens/ProfileScreen.cs @@ -371,11 +371,11 @@ private void calculateProfiles(string[] usernames) var filteredPlays = new List(); // List of all beatmap IDs in plays without duplicates - var beatmapIDs = plays.Select(x => x.SoloScore.BeatmapID).Distinct().ToList(); + var beatmapIDs = plays.Select(x => x.Score.Beatmap.OnlineID).Distinct().ToList(); foreach (int id in beatmapIDs) { - var bestPlayOnBeatmap = plays.Where(x => x.SoloScore.BeatmapID == id).OrderByDescending(x => x.PerformanceAttributes?.Total).First(); + var bestPlayOnBeatmap = plays.Where(x => x.Score.Beatmap.OnlineID == id).OrderByDescending(x => x.PerformanceAttributes?.Total).First(); filteredPlays.Add(bestPlayOnBeatmap); } @@ -472,15 +472,15 @@ private void updateSorting(ProfileSortCriteria sortCriteria) switch (sortCriteria) { case ProfileSortCriteria.Live: - sortedScores = scores.Children.OrderByDescending(x => x.Score.LivePP).ToArray(); + sortedScores = scores.Children.OrderByDescending(x => x.ExtScore.LivePP).ToArray(); break; case ProfileSortCriteria.Local: - sortedScores = scores.Children.OrderByDescending(x => x.Score.PerformanceAttributes?.Total).ToArray(); + sortedScores = scores.Children.OrderByDescending(x => x.ExtScore.PerformanceAttributes?.Total).ToArray(); break; case ProfileSortCriteria.Difference: - sortedScores = scores.Children.OrderByDescending(x => x.Score.PerformanceAttributes?.Total - x.Score.LivePP).ToArray(); + sortedScores = scores.Children.OrderByDescending(x => x.ExtScore.PerformanceAttributes?.Total - x.ExtScore.LivePP).ToArray(); break; default: From 82e27e114b2895a297cfd8c155c692167b6e3c50 Mon Sep 17 00:00:00 2001 From: KermitNuggies Date: Sat, 14 Feb 2026 19:35:52 +1300 Subject: [PATCH 2/2] add some comments justifying manual conversions --- .../Components/ExtendedProfileScore.cs | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs b/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs index e5d08ce551..86ad358fff 100644 --- a/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs +++ b/PerformanceCalculatorGUI/Components/ExtendedProfileScore.cs @@ -56,23 +56,31 @@ public ExtendedScore(IScoreInfo score, DifficultyAttributes difficultyAttributes LivePP = score.PP; } - public Dictionary? Statistics() + // IScoreInfo is missing statistics right now, but both SoloScoreInfo and ScoreInfo have it (and I believe it's planned for future) + // I think handling conversion at this level is the most elegant, although it means supporting any additional types that inherit from IScoreInfo will need to be manually added here + public Dictionary? Statistics { - if (Score is SoloScoreInfo soloScore) - return soloScore.Statistics; - if (Score is ScoreInfo scoreInfo) - return scoreInfo.Statistics; - return null; + get + { + if (Score is SoloScoreInfo soloScore) + return soloScore.Statistics; + if (Score is ScoreInfo scoreInfo) + return scoreInfo.Statistics; + return null; + } } - public APIMod[]? Mods() + // Exists for largely the same reasoning as Statistics, except rather than a missing field it's because APIMod doesn't inherit from IMod at all + // This returns APIMod[]? instead of Mod[]? because it means ruleset handling isn't necessary + public APIMod[]? Mods { - if (Score is ScoreInfo scoreInfo) - return scoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); - if (Score is SoloScoreInfo soloScoreInfo) + get { - return soloScoreInfo.Mods; + if (Score is ScoreInfo scoreInfo) + return scoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); + if (Score is SoloScoreInfo soloScoreInfo) + return soloScoreInfo.Mods; + return null; } - return null; } } @@ -307,7 +315,7 @@ private void load(GameHost host, RulesetStore rulesets) formatCombo(), new OsuSpriteText { - Text = $"{{ {formatStatistics(ExtScore.Statistics(), scoreRuleset)} }}", + Text = $"{{ {formatStatistics(ExtScore.Statistics, scoreRuleset)} }}", Font = OsuFont.GetFont(size: small_text_font_size, weight: FontWeight.Regular), Colour = colourProvider.Light2, Anchor = Anchor.TopCentre, @@ -354,7 +362,7 @@ private void load(GameHost host, RulesetStore rulesets) Origin = Anchor.CentreRight, Direction = FillDirection.Horizontal, Spacing = new Vector2(2), - Children = ExtScore.Mods().Select(mod => new ModIcon(mod.ToMod(scoreRuleset)) + Children = ExtScore.Mods.Select(mod => new ModIcon(mod.ToMod(scoreRuleset)) { Scale = new Vector2(0.35f) }).ToList(),