From 69351d2cdf9de1b4c01ef5740c73983cb3a94fe2 Mon Sep 17 00:00:00 2001 From: Noah M Date: Wed, 18 May 2022 01:09:58 -0500 Subject: [PATCH 01/75] Implement button to delete all beatmap videos --- osu.Game/Beatmaps/BeatmapManager.cs | 12 ++++++ .../MaintenanceSettingsStrings.cs | 5 +++ .../Sections/Maintenance/GeneralSettings.cs | 14 +++++++ .../MassDeleteConfirmationDialog.cs | 7 ++++ osu.Game/Stores/RealmArchiveModelManager.cs | 38 +++++++++++++++++++ 5 files changed, 76 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5f7de0d762..2a5df9e206 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,6 +319,18 @@ namespace osu.Game.Beatmaps }); } + public void DeleteVideos(Expression>? filter = null, bool silent = false) + { + realm.Write(r => + { + var items = r.All().Where(s => !s.DeletePending && !s.Protected); + + if (filter != null) + items = items.Where(filter); + beatmapModelManager.DeleteVideos(items.ToList(), silent); + }); + } + public void UndeleteAll() { realm.Run(r => beatmapModelManager.Undelete(r.All().Where(s => s.DeletePending).ToList())); diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index a0e1a9ddab..7a04bcd1ca 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -29,6 +29,11 @@ namespace osu.Game.Localisation /// public static LocalisableString DeleteAllBeatmaps => new TranslatableString(getKey(@"delete_all_beatmaps"), @"Delete ALL beatmaps"); + /// + /// "Delete ALL beatmap videos" + /// + public static LocalisableString DeleteAllBeatmapVideos => new TranslatableString(getKey(@"delete_all_beatmap_videos"), @"Delete ALL beatmap videos"); + /// /// "Import scores from stable" /// diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index be4b0decd9..c2ebb59ecc 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private SettingsButton deleteSkinsButton; private SettingsButton restoreButton; private SettingsButton undeleteButton; + private SettingsButton deleteBeatmapVideosButton; [BackgroundDependencyLoader(permitNulls: true)] private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) @@ -58,6 +59,19 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); + Add(deleteBeatmapVideosButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, + Action = () => + { + dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => + { + deleteBeatmapVideosButton.Enabled.Value = false; + Task.Run(() => beatmaps.DeleteVideos()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); + })); + } + }); + if (legacyImportManager?.SupportsImportFromStable == true) { Add(importScoresButton = new SettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs index c481c80d82..274b1060ca 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs @@ -29,4 +29,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }; } } + public class MassVideoDeleteConfirmationDialog : MassDeleteConfirmationDialog + { + public MassVideoDeleteConfirmationDialog(Action deleteAction) : base(deleteAction) + { + BodyText = "All beatmap videos? This cannot be undone!"; + } + } } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index cc8229b436..f70aa41cfb 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -132,6 +132,44 @@ namespace osu.Game.Stores notification.State = ProgressNotificationState.Completed; } + /// + /// Delete videos from a list of items. + /// This will post notifications tracking progress. + /// + public void DeleteVideos(List items, bool silent = false) + { + if (items.Count == 0) return; + + var notification = new ProgressNotification + { + Progress = 0, + Text = $"Preparing to delete all {HumanisedModelName} videos...", + CompletionText = $"Deleted all {HumanisedModelName} videos!", + State = ProgressNotificationState.Active, + }; + if (!silent) + PostNotification?.Invoke(notification); + + int i = 0; + + foreach (var b in items) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + notification.Text = $"Deleting videos from {HumanisedModelName}s ({++i} of {items.Count})"; + + var video = b.Files.FirstOrDefault(f => f.Filename.EndsWith(".mp4") || f.Filename.EndsWith(".avi") || f.Filename.EndsWith(".mov") || f.Filename.EndsWith(".flv")); + if (video != null) + DeleteFile(b, video); + + notification.Progress = (float)i / items.Count; + } + + notification.State = ProgressNotificationState.Completed; + } + /// /// Restore multiple items that were previously deleted. /// This will post notifications tracking progress. From 617382a56f19d628122f5842d8e939efbf2ecbad Mon Sep 17 00:00:00 2001 From: Noah M Date: Wed, 18 May 2022 15:20:02 -0500 Subject: [PATCH 02/75] Split off MassVideoDeleteConfirmationDialog into its own file --- .../Maintenance/MassDeleteConfirmationDialog.cs | 7 ------- .../MassVideoDeleteConfirmationDialog.cs | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs index 274b1060ca..c481c80d82 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs @@ -29,11 +29,4 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }; } } - public class MassVideoDeleteConfirmationDialog : MassDeleteConfirmationDialog - { - public MassVideoDeleteConfirmationDialog(Action deleteAction) : base(deleteAction) - { - BodyText = "All beatmap videos? This cannot be undone!"; - } - } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs new file mode 100644 index 0000000000..522177807c --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class MassVideoDeleteConfirmationDialog : MassDeleteConfirmationDialog + { + public MassVideoDeleteConfirmationDialog(Action deleteAction) : base(deleteAction) + { + BodyText = "All beatmap videos? This cannot be undone!"; + } + } +} From a93a2fcafba1b0f3af7e52ea6b36e72a592b9e51 Mon Sep 17 00:00:00 2001 From: Noah M Date: Wed, 18 May 2022 15:37:46 -0500 Subject: [PATCH 03/75] Move implementation of DeleteVideos to BeatmapModelManager --- osu.Game/Beatmaps/BeatmapModelManager.cs | 39 +++++++++++++++++++++ osu.Game/Stores/RealmArchiveModelManager.cs | 38 -------------------- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 4c680bbcc9..3cd2e8c8bd 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -16,6 +16,7 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Skinning; using osu.Game.Stores; +using osu.Game.Overlays.Notifications; #nullable enable @@ -114,5 +115,43 @@ namespace osu.Game.Beatmaps item.CopyChangesToRealm(existing); }); } + + /// + /// Delete videos from a list of beatmaps. + /// This will post notifications tracking progress. + /// + public void DeleteVideos(List items, bool silent = false) + { + if (items.Count == 0) return; + + var notification = new ProgressNotification + { + Progress = 0, + Text = $"Preparing to delete all {HumanisedModelName} videos...", + CompletionText = $"Deleted all {HumanisedModelName} videos!", + State = ProgressNotificationState.Active, + }; + if (!silent) + PostNotification?.Invoke(notification); + + int i = 0; + + foreach (var b in items) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + notification.Text = $"Deleting videos from {HumanisedModelName}s ({++i} of {items.Count})"; + + var video = b.Files.FirstOrDefault(f => f.Filename.EndsWith(".mp4") || f.Filename.EndsWith(".avi") || f.Filename.EndsWith(".mov") || f.Filename.EndsWith(".flv")); + if (video != null) + DeleteFile(b, video); + + notification.Progress = (float)i / items.Count; + } + + notification.State = ProgressNotificationState.Completed; + } } } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index f70aa41cfb..cc8229b436 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -132,44 +132,6 @@ namespace osu.Game.Stores notification.State = ProgressNotificationState.Completed; } - /// - /// Delete videos from a list of items. - /// This will post notifications tracking progress. - /// - public void DeleteVideos(List items, bool silent = false) - { - if (items.Count == 0) return; - - var notification = new ProgressNotification - { - Progress = 0, - Text = $"Preparing to delete all {HumanisedModelName} videos...", - CompletionText = $"Deleted all {HumanisedModelName} videos!", - State = ProgressNotificationState.Active, - }; - if (!silent) - PostNotification?.Invoke(notification); - - int i = 0; - - foreach (var b in items) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; - - notification.Text = $"Deleting videos from {HumanisedModelName}s ({++i} of {items.Count})"; - - var video = b.Files.FirstOrDefault(f => f.Filename.EndsWith(".mp4") || f.Filename.EndsWith(".avi") || f.Filename.EndsWith(".mov") || f.Filename.EndsWith(".flv")); - if (video != null) - DeleteFile(b, video); - - notification.Progress = (float)i / items.Count; - } - - notification.State = ProgressNotificationState.Completed; - } - /// /// Restore multiple items that were previously deleted. /// This will post notifications tracking progress. From 60eb0ea69edd1c8e9a9b41c2db071d385536493e Mon Sep 17 00:00:00 2001 From: Noah M Date: Wed, 18 May 2022 15:40:46 -0500 Subject: [PATCH 04/75] Remove unnecessary parameters from DeleteVideos --- osu.Game/Beatmaps/BeatmapManager.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 2a5df9e206..92266924df 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,15 +319,12 @@ namespace osu.Game.Beatmaps }); } - public void DeleteVideos(Expression>? filter = null, bool silent = false) + public void DeleteVideos() { realm.Write(r => { var items = r.All().Where(s => !s.DeletePending && !s.Protected); - - if (filter != null) - items = items.Where(filter); - beatmapModelManager.DeleteVideos(items.ToList(), silent); + beatmapModelManager.DeleteVideos(items.ToList()); }); } From d1fcd73c87ca9fe93123ad66b0259521587d15a0 Mon Sep 17 00:00:00 2001 From: Noah M Date: Wed, 18 May 2022 16:25:10 -0500 Subject: [PATCH 05/75] Convert extension checking to checking against string array --- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 3cd2e8c8bd..9d3ea7d192 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -34,6 +34,8 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; + private static readonly string[] video_extensions = { ".mp4", ".mov", ".avi", ".flv" }; + public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) : base(realm, storage, onlineLookupQueue) { @@ -144,7 +146,7 @@ namespace osu.Game.Beatmaps notification.Text = $"Deleting videos from {HumanisedModelName}s ({++i} of {items.Count})"; - var video = b.Files.FirstOrDefault(f => f.Filename.EndsWith(".mp4") || f.Filename.EndsWith(".avi") || f.Filename.EndsWith(".mov") || f.Filename.EndsWith(".flv")); + var video = b.Files.FirstOrDefault(f => video_extensions.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal))); if (video != null) DeleteFile(b, video); From 0ef94067872805047679d948585ce6fb81d8a463 Mon Sep 17 00:00:00 2001 From: Noah M Date: Wed, 18 May 2022 16:35:14 -0500 Subject: [PATCH 06/75] Fix deleteBeatmapVideosButton not being reenabled My bad --- .../Overlays/Settings/Sections/Maintenance/GeneralSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index c2ebb59ecc..afc5cad893 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => { deleteBeatmapVideosButton.Enabled.Value = false; - Task.Run(() => beatmaps.DeleteVideos()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); + Task.Run(() => beatmaps.DeleteVideos()).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); })); } }); From b550cbc5a3e22421073401059c43c7b54095a623 Mon Sep 17 00:00:00 2001 From: Noah M Date: Wed, 25 May 2022 17:01:30 -0500 Subject: [PATCH 07/75] Fix failing code quality checks --- .../Overlays/Settings/Sections/Maintenance/GeneralSettings.cs | 2 +- .../Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index afc5cad893..37758875ea 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => { deleteBeatmapVideosButton.Enabled.Value = false; - Task.Run(() => beatmaps.DeleteVideos()).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); + Task.Run(beatmaps.DeleteVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); })); } }); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs index 522177807c..fc8c9d497b 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs @@ -7,7 +7,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class MassVideoDeleteConfirmationDialog : MassDeleteConfirmationDialog { - public MassVideoDeleteConfirmationDialog(Action deleteAction) : base(deleteAction) + public MassVideoDeleteConfirmationDialog(Action deleteAction) + : base(deleteAction) { BodyText = "All beatmap videos? This cannot be undone!"; } From c97b477485bc87d5841c6dffd884ad37971a51e5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 30 May 2022 19:11:54 +0900 Subject: [PATCH 08/75] Fix inverted operation order --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 51c1e6b43b..b68ac3cb05 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -213,8 +213,8 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(DrawableRuleset); ScoreProcessor = ruleset.CreateScoreProcessor(); - ScoreProcessor.ApplyBeatmap(playableBeatmap); ScoreProcessor.Mods.Value = gameplayMods; + ScoreProcessor.ApplyBeatmap(playableBeatmap); dependencies.CacheAs(ScoreProcessor); From a052e09ac3d4a9e8c0aa3a46f0bf859f3bab60a0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 30 May 2022 19:14:03 +0900 Subject: [PATCH 09/75] Send ScoreProcessor statistics in SpectatorState --- osu.Game/Online/Spectator/SpectatorClient.cs | 3 + osu.Game/Online/Spectator/SpectatorState.cs | 18 ++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 59 ++++++++++---------- osu.Game/Screens/Play/GameplayState.cs | 12 ++-- osu.Game/Screens/Play/Player.cs | 2 +- 5 files changed, 61 insertions(+), 33 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 78beda6298..3c31acca01 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -172,6 +172,9 @@ namespace osu.Game.Online.Spectator currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); currentState.State = SpectatedUserState.Playing; + currentState.MaxAchievableCombo = state.ScoreProcessor.MaxAchievableCombo; + currentState.MaxAchievableBaseScore = state.ScoreProcessor.MaxAchievableBaseScore; + currentState.TotalBasicHitObjects = state.ScoreProcessor.TotalBasicHitObjects; currentBeatmap = state.Beatmap; currentScore = score; diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 77686d12da..8b2e90ead0 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -27,6 +27,24 @@ namespace osu.Game.Online.Spectator [Key(3)] public SpectatedUserState State { get; set; } + /// + /// The maximum achievable combo, if everything is hit perfectly. + /// + [Key(4)] + public int MaxAchievableCombo { get; set; } + + /// + /// The maximum achievable base score, if everything is hit perfectly. + /// + [Key(5)] + public double MaxAchievableBaseScore { get; set; } + + /// + /// The total number of basic (non-tick and non-bonus) hitobjects that can be hit. + /// + [Key(6)] + public int TotalBasicHitObjects { get; set; } + public bool Equals(SpectatorState other) { if (ReferenceEquals(null, other)) return false; diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 1dd1d1aeb6..ec09bfcfa3 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -88,17 +88,20 @@ namespace osu.Game.Rulesets.Scoring private readonly double accuracyPortion; private readonly double comboPortion; - private int maxAchievableCombo; + /// + /// The maximum achievable combo, if everything is hit perfectly. + /// + internal int MaxAchievableCombo; /// - /// The maximum achievable base score. + /// The maximum achievable base score, if everything is hit perfectly. /// - private double maxBaseScore; + internal double MaxAchievableBaseScore; /// - /// The maximum number of basic (non-tick and non-bonus) hitobjects. + /// The total number of basic (non-tick and non-bonus) hitobjects that can be hit. /// - private int maxBasicHitObjects; + internal int TotalBasicHitObjects; /// /// The maximum of a basic (non-tick and non-bonus) hitobject. @@ -106,9 +109,9 @@ namespace osu.Game.Rulesets.Scoring /// private HitResult? maxBasicResult; - private double rollingMaxBaseScore; - private double baseScore; - private int basicHitObjects; + private double rollingMaxAchievableBaseScore; + private double rollingBaseScore; + private int rollingBasicHitObjects; private bool beatmapApplied; private readonly Dictionary scoreResultCounts = new Dictionary(); @@ -175,12 +178,12 @@ namespace osu.Game.Rulesets.Scoring if (!result.Type.IsBonus()) { - baseScore += scoreIncrease; - rollingMaxBaseScore += result.Judgement.MaxNumericResult; + rollingBaseScore += scoreIncrease; + rollingMaxAchievableBaseScore += result.Judgement.MaxNumericResult; } if (result.Type.IsBasic()) - basicHitObjects++; + rollingBasicHitObjects++; hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -213,12 +216,12 @@ namespace osu.Game.Rulesets.Scoring if (!result.Type.IsBonus()) { - baseScore -= scoreIncrease; - rollingMaxBaseScore -= result.Judgement.MaxNumericResult; + rollingBaseScore -= scoreIncrease; + rollingMaxAchievableBaseScore -= result.Judgement.MaxNumericResult; } if (result.Type.IsBasic()) - basicHitObjects--; + rollingBasicHitObjects--; Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; @@ -229,12 +232,12 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { - double rollingAccuracyRatio = rollingMaxBaseScore > 0 ? baseScore / rollingMaxBaseScore : 1; - double accuracyRatio = maxBaseScore > 0 ? baseScore / maxBaseScore : 1; - double comboRatio = maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1; + double rollingAccuracyRatio = rollingMaxAchievableBaseScore > 0 ? rollingBaseScore / rollingMaxAchievableBaseScore : 1; + double accuracyRatio = MaxAchievableBaseScore > 0 ? rollingBaseScore / MaxAchievableBaseScore : 1; + double comboRatio = MaxAchievableCombo > 0 ? (double)HighestCombo.Value / MaxAchievableCombo : 1; Accuracy.Value = rollingAccuracyRatio; - TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), maxBasicHitObjects); + TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), TotalBasicHitObjects); } /// @@ -288,10 +291,10 @@ namespace osu.Game.Rulesets.Scoring out _, out _); - double accuracyRatio = maxBaseScore > 0 ? extractedBaseScore / maxBaseScore : 1; - double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; + double accuracyRatio = MaxAchievableBaseScore > 0 ? extractedBaseScore / MaxAchievableBaseScore : 1; + double comboRatio = MaxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / MaxAchievableCombo : 1; - return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), maxBasicHitObjects); + return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), TotalBasicHitObjects); } /// @@ -403,14 +406,14 @@ namespace osu.Game.Rulesets.Scoring if (storeResults) { - maxAchievableCombo = HighestCombo.Value; - maxBaseScore = baseScore; - maxBasicHitObjects = basicHitObjects; + MaxAchievableCombo = HighestCombo.Value; + MaxAchievableBaseScore = rollingBaseScore; + TotalBasicHitObjects = rollingBasicHitObjects; } - baseScore = 0; - rollingMaxBaseScore = 0; - basicHitObjects = 0; + rollingBaseScore = 0; + rollingMaxAchievableBaseScore = 0; + rollingBasicHitObjects = 0; TotalScore.Value = 0; Accuracy.Value = 1; @@ -444,7 +447,7 @@ namespace osu.Game.Rulesets.Scoring if (frame.Header == null) return; - extractFromStatistics(ruleset, frame.Header.Statistics, out baseScore, out rollingMaxBaseScore, out _, out _); + extractFromStatistics(ruleset, frame.Header.Statistics, out rollingBaseScore, out rollingMaxAchievableBaseScore, out _, out _); HighestCombo.Value = frame.Header.MaxCombo; scoreResultCounts.Clear(); diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index c6a072da74..586cdcda51 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; using osu.Framework.Bindables; @@ -8,10 +10,9 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; -#nullable enable - namespace osu.Game.Screens.Play { /// @@ -39,6 +40,8 @@ namespace osu.Game.Screens.Play /// public readonly Score Score; + public readonly ScoreProcessor ScoreProcessor; + /// /// Whether gameplay completed without the user failing. /// @@ -61,7 +64,7 @@ namespace osu.Game.Screens.Play private readonly Bindable lastJudgementResult = new Bindable(); - public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null) + public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null, ScoreProcessor? scoreProcessor = null) { Beatmap = beatmap; Ruleset = ruleset; @@ -72,7 +75,8 @@ namespace osu.Game.Screens.Play Ruleset = ruleset.RulesetInfo } }; - Mods = mods ?? ArraySegment.Empty; + Mods = mods ?? Array.Empty(); + ScoreProcessor = scoreProcessor ?? ruleset.CreateScoreProcessor(); } /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b68ac3cb05..dfc0fa1d1d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -237,7 +237,7 @@ namespace osu.Game.Screens.Play Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; Score.ScoreInfo.Mods = gameplayMods; - dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score)); + dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor)); var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin); From 75b50de26971218024636fcdda74df87dbbc160b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 30 May 2022 19:15:30 +0900 Subject: [PATCH 10/75] Implement score processor for spectator states --- .../Spectator/SpectatorScoreProcessor.cs | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 osu.Game/Online/Spectator/SpectatorScoreProcessor.cs diff --git a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs new file mode 100644 index 0000000000..99596fa1d3 --- /dev/null +++ b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs @@ -0,0 +1,204 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Online.Spectator +{ + /// + /// A wrapper over a for spectated users. + /// This should be used when a local "playable" beatmap is unavailable or expensive to generate for the spectated user. + /// + public class SpectatorScoreProcessor : Component + { + /// + /// The current total score. + /// + public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 }; + + /// + /// The current accuracy. + /// + public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; + + /// + /// The current combo. + /// + public readonly BindableInt Combo = new BindableInt(); + + /// + /// The used to calculate scores. + /// + public readonly Bindable Mode = new Bindable(); + + /// + /// The applied s. + /// + public IReadOnlyList Mods => scoreProcessor?.Mods.Value ?? Array.Empty(); + + [Resolved] + private SpectatorClient spectatorClient { get; set; } = null!; + + [Resolved] + private RulesetStore rulesetStore { get; set; } = null!; + + private readonly IBindableDictionary spectatorStates = new BindableDictionary(); + private readonly List replayFrames = new List(); + private readonly int userId; + + private ScoreProcessor? scoreProcessor; + private ScoreInfo? scoreInfo; + + public SpectatorScoreProcessor(int userId) + { + this.userId = userId; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Mode.BindValueChanged(_ => UpdateScore()); + + spectatorStates.BindTo(spectatorClient.WatchedUserStates); + spectatorStates.BindCollectionChanged(onSpectatorStatesChanged, true); + + spectatorClient.OnNewFrames += onNewFrames; + } + + private void onSpectatorStatesChanged(object? sender, NotifyDictionaryChangedEventArgs e) + { + if (!spectatorStates.TryGetValue(userId, out var userState) || userState.BeatmapID == null || userState.RulesetID == null) + { + scoreProcessor?.RemoveAndDisposeImmediately(); + scoreProcessor = null; + scoreInfo = null; + replayFrames.Clear(); + return; + } + + if (scoreProcessor != null) + return; + + Debug.Assert(scoreInfo == null); + + RulesetInfo? rulesetInfo = rulesetStore.GetRuleset(userState.RulesetID.Value); + if (rulesetInfo == null) + return; + + Ruleset ruleset = rulesetInfo.CreateInstance(); + + scoreInfo = new ScoreInfo { Ruleset = rulesetInfo }; + scoreProcessor = ruleset.CreateScoreProcessor(); + + // Mods are required for score multiplier. + scoreProcessor.Mods.Value = userState.Mods.Select(m => m.ToMod(ruleset)).ToArray(); + + // Applying beatmap required to call ComputePartialScore(). + scoreProcessor.ApplyBeatmap(new DummyBeatmap()); + + scoreProcessor.MaxAchievableCombo = userState.MaxAchievableCombo; + scoreProcessor.MaxAchievableBaseScore = userState.MaxAchievableBaseScore; + scoreProcessor.TotalBasicHitObjects = userState.TotalBasicHitObjects; + } + + private void onNewFrames(int incomingUserId, FrameDataBundle bundle) + { + if (incomingUserId != userId) + return; + + Schedule(() => + { + if (scoreProcessor == null) + return; + + replayFrames.Add(new TimedFrame(bundle.Frames.First().Time, bundle.Header)); + UpdateScore(); + }); + } + + public void UpdateScore() + { + if (scoreInfo == null || replayFrames.Count == 0) + return; + + Debug.Assert(scoreProcessor != null); + + int frameIndex = replayFrames.BinarySearch(new TimedFrame(Time.Current)); + if (frameIndex < 0) + frameIndex = ~frameIndex; + frameIndex = Math.Clamp(frameIndex - 1, 0, replayFrames.Count - 1); + + TimedFrame frame = replayFrames[frameIndex]; + Debug.Assert(frame.Header != null); + + scoreInfo.MaxCombo = frame.Header.MaxCombo; + scoreInfo.Statistics = frame.Header.Statistics; + + Accuracy.Value = frame.Header.Accuracy; + Combo.Value = frame.Header.Combo; + TotalScore.Value = scoreProcessor.ComputePartialScore(Mode.Value, scoreInfo); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (spectatorClient != null) + spectatorClient.OnNewFrames -= onNewFrames; + } + + private class DummyBeatmap : IBeatmap + { + public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo(); + public BeatmapMetadata Metadata { get; } = new BeatmapMetadata(); + public BeatmapDifficulty Difficulty { get; set; } = new BeatmapDifficulty(); + public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo(); + public List Breaks { get; } = new List(); + public double TotalBreakTime => 0; + public IReadOnlyList HitObjects => Array.Empty(); + + public IEnumerable GetStatistics() => Array.Empty(); + + public double GetMostCommonBeatLength() => 0; + + public IBeatmap Clone() => throw new NotImplementedException(); + } + + private class TimedFrame : IComparable + { + public readonly double Time; + public readonly FrameHeader? Header; + + public TimedFrame(double time) + { + Time = time; + } + + public TimedFrame(double time, FrameHeader header) + { + Time = time; + Header = header; + } + + public int CompareTo(TimedFrame other) => Time.CompareTo(other.Time); + } + } +} From 22d998dc2a518728c00d1f2d84c40aef12479110 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 30 May 2022 19:18:38 +0900 Subject: [PATCH 11/75] Use new score processor in MultiplayerGameplayLeaderboard --- ...MultiplayerGameplayLeaderboardTestScene.cs | 35 +++-- .../TestSceneMultiSpectatorLeaderboard.cs | 6 +- ...TestSceneMultiplayerGameplayLeaderboard.cs | 42 ++++- ...ceneMultiplayerGameplayLeaderboardTeams.cs | 5 +- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- .../Multiplayer/MultiplayerPlayer.cs | 11 +- .../Spectate/MultiSpectatorLeaderboard.cs | 48 +----- .../Spectate/MultiSpectatorScreen.cs | 8 +- .../HUD/MultiplayerGameplayLeaderboard.cs | 147 +++++------------- 9 files changed, 110 insertions(+), 194 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs index 4e6342868a..62cea378e6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs @@ -18,7 +18,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; @@ -27,7 +26,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public abstract class MultiplayerGameplayLeaderboardTestScene : OsuTestScene { - private const int total_users = 16; + protected const int TOTAL_USERS = 16; protected readonly BindableList MultiplayerUsers = new BindableList(); @@ -35,9 +34,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected virtual MultiplayerRoomUser CreateUser(int userId) => new MultiplayerRoomUser(userId); - protected abstract MultiplayerGameplayLeaderboard CreateLeaderboard(OsuScoreProcessor scoreProcessor); + protected abstract MultiplayerGameplayLeaderboard CreateLeaderboard(); private readonly BindableList multiplayerUserIds = new BindableList(); + private readonly BindableDictionary watchedUserStates = new BindableDictionary(); private OsuConfigManager config; @@ -81,6 +81,9 @@ namespace osu.Game.Tests.Visual.Multiplayer multiplayerClient.SetupGet(c => c.CurrentMatchPlayingUserIds) .Returns(() => multiplayerUserIds); + + spectatorClient.SetupGet(c => c.WatchedUserStates) + .Returns(() => watchedUserStates); } [SetUpSteps] @@ -100,8 +103,23 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("populate users", () => { MultiplayerUsers.Clear(); - for (int i = 0; i < total_users; i++) - MultiplayerUsers.Add(CreateUser(i)); + + for (int i = 0; i < TOTAL_USERS; i++) + { + var user = CreateUser(i); + + MultiplayerUsers.Add(user); + + watchedUserStates[i] = new SpectatorState + { + BeatmapID = 0, + RulesetID = 0, + Mods = user.Mods, + MaxAchievableCombo = 1000, + MaxAchievableBaseScore = 10000, + TotalBasicHitObjects = 1000 + }; + } }); AddStep("create leaderboard", () => @@ -109,13 +127,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Leaderboard?.Expire(); Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); - var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - OsuScoreProcessor scoreProcessor = new OsuScoreProcessor(); - scoreProcessor.ApplyBeatmap(playableBeatmap); - Child = scoreProcessor; - - LoadComponentAsync(Leaderboard = CreateLeaderboard(scoreProcessor), Add); + LoadComponentAsync(Leaderboard = CreateLeaderboard(), Add); }); AddUntilStep("wait for load", () => Leaderboard.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index f57a54d84c..e94e198a4a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -8,7 +8,6 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; @@ -42,11 +41,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create leaderboard", () => { Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); - var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - var scoreProcessor = new OsuScoreProcessor(); - scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) { Expanded = { Value = true } }, Add); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 6e4aa48b0e..cbbd535cee 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -1,22 +1,58 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; +using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerGameplayLeaderboardTestScene { - protected override MultiplayerGameplayLeaderboard CreateLeaderboard(OsuScoreProcessor scoreProcessor) + protected override MultiplayerRoomUser CreateUser(int userId) { - return new MultiplayerGameplayLeaderboard(Ruleset.Value, scoreProcessor, MultiplayerUsers.ToArray()) + var user = base.CreateUser(userId); + + if (userId == TOTAL_USERS - 1) + user.Mods = new[] { new APIMod(new OsuModNoFail()) }; + + return user; + } + + protected override MultiplayerGameplayLeaderboard CreateLeaderboard() + { + return new TestLeaderboard(MultiplayerUsers.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, }; } + + [Test] + public void TestPerUserMods() + { + AddStep("first user has no mods", () => Assert.That(((TestLeaderboard)Leaderboard).UserMods[0], Is.Empty)); + AddStep("last user has NF mod", () => + { + Assert.That(((TestLeaderboard)Leaderboard).UserMods[TOTAL_USERS - 1], Has.One.Items); + Assert.That(((TestLeaderboard)Leaderboard).UserMods[TOTAL_USERS - 1].Single(), Is.TypeOf()); + }); + } + + private class TestLeaderboard : MultiplayerGameplayLeaderboard + { + public Dictionary> UserMods => UserScores.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ScoreProcessor.Mods); + + public TestLeaderboard(MultiplayerRoomUser[] users) + : base(users) + { + } + } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 5caab9487e..c25884039f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -5,7 +5,6 @@ using System.Linq; using osu.Framework.Graphics; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; -using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play.HUD; @@ -25,8 +24,8 @@ namespace osu.Game.Tests.Visual.Multiplayer return user; } - protected override MultiplayerGameplayLeaderboard CreateLeaderboard(OsuScoreProcessor scoreProcessor) => - new MultiplayerGameplayLeaderboard(Ruleset.Value, scoreProcessor, MultiplayerUsers.ToArray()) + protected override MultiplayerGameplayLeaderboard CreateLeaderboard() => + new MultiplayerGameplayLeaderboard(MultiplayerUsers.ToArray()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 3c31acca01..527f6d06ce 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -39,7 +39,7 @@ namespace osu.Game.Online.Spectator /// /// The states of all users currently being watched. /// - public IBindableDictionary WatchedUserStates => watchedUserStates; + public virtual IBindableDictionary WatchedUserStates => watchedUserStates; /// /// A global list of all players currently playing. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 3bf8a09cb9..f4bfec169d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -82,7 +82,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer LocalUserPlaying.BindValueChanged(_ => updateLeaderboardExpandedState(), true); // todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area. - LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(GameplayState.Ruleset.RulesetInfo, ScoreProcessor, users), l => + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(users), l => { if (!LoadedBeatmapSuccessfully) return; @@ -149,16 +149,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override void StartGameplay() { - // We can enter this screen one of two ways: - // 1. Via the automatic natural progression of PlayerLoader into Player. - // We'll arrive here in a Loaded state, and we need to let the server know that we're ready to start. - // 2. Via the server forcefully starting gameplay because players have been hanging out in PlayerLoader for too long. - // We'll arrive here in a Playing state, and we should neither show the loading spinner nor tell the server that we're ready to start (gameplay has already started). - // - // The base call is blocked here because in both cases gameplay is started only when the server says so via onGameplayStarted(). - if (client.LocalUser?.State == MultiplayerUserState.Loaded) { + // block base call, but let the server know we are ready to start. loadingDisplay.Show(); client.ChangeState(MultiplayerUserState.ReadyForGameplay); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs index 4545913db8..589f5ce988 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs @@ -2,19 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; -using JetBrains.Annotations; using osu.Framework.Timing; using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard { - public MultiSpectatorLeaderboard(RulesetInfo ruleset, [NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users) - : base(ruleset, scoreProcessor, users) + public MultiSpectatorLeaderboard(MultiplayerRoomUser[] users) + : base(users) { } @@ -23,52 +20,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (!UserScores.TryGetValue(userId, out var data)) throw new ArgumentException(@"Provided user is not tracked by this leaderboard", nameof(userId)); - ((SpectatingTrackedUserData)data).Clock = clock; + data.ScoreProcessor.Clock = new FramedClock(clock); } - public void RemoveClock(int userId) - { - if (!UserScores.TryGetValue(userId, out var data)) - throw new ArgumentException(@"Provided user is not tracked by this leaderboard", nameof(userId)); - - ((SpectatingTrackedUserData)data).Clock = null; - } - - protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, ruleset, scoreProcessor); - protected override void Update() { base.Update(); foreach (var (_, data) in UserScores) - data.UpdateScore(); - } - - private class SpectatingTrackedUserData : TrackedUserData - { - [CanBeNull] - public IClock Clock; - - public SpectatingTrackedUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) - : base(user, ruleset, scoreProcessor) - { - } - - public override void UpdateScore() - { - if (Frames.Count == 0) - return; - - if (Clock == null) - return; - - int frameIndex = Frames.BinarySearch(new TimedFrame(Clock.CurrentTime)); - if (frameIndex < 0) - frameIndex = ~frameIndex; - frameIndex = Math.Clamp(frameIndex - 1, 0, Frames.Count - 1); - - SetFrame(Frames[frameIndex]); - } + data.ScoreProcessor.UpdateScore(); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 2d03276fe5..d9c19cdfdd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -128,12 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate syncManager.AddPlayerClock(instances[i].GameplayClock); } - // Todo: This is not quite correct - it should be per-user to adjust for other mod combinations. - var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor(); - scoreProcessor.ApplyBeatmap(playableBeatmap); - - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, users) + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(users) { Expanded = { Value = true }, }, l => @@ -240,7 +235,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate instance.FadeColour(colours.Gray4, 400, Easing.OutQuint); syncManager.RemovePlayerClock(instance.GameplayClock); - leaderboard.RemoveClock(userId); } public override bool OnBackButton() diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 41b40e9a91..5ee6000cf0 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -17,9 +18,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Spectator; -using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD @@ -43,8 +42,6 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private UserLookupCache userLookupCache { get; set; } - private readonly RulesetInfo ruleset; - private readonly ScoreProcessor scoreProcessor; private readonly MultiplayerRoomUser[] playingUsers; private Bindable scoringMode; @@ -55,57 +52,56 @@ namespace osu.Game.Screens.Play.HUD /// /// Construct a new leaderboard. /// - /// The ruleset. - /// A score processor instance to handle score calculation for scores of users in the match. /// IDs of all users in this match. - public MultiplayerGameplayLeaderboard(RulesetInfo ruleset, ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users) + public MultiplayerGameplayLeaderboard(MultiplayerRoomUser[] users) { - // todo: this will eventually need to be created per user to support different mod combinations. - this.ruleset = ruleset; - this.scoreProcessor = scoreProcessor; - playingUsers = users; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, IAPIProvider api) + private void load(OsuConfigManager config, IAPIProvider api, CancellationToken cancellationToken) { scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); foreach (var user in playingUsers) { - var trackedUser = CreateUserData(user, ruleset, scoreProcessor); - - trackedUser.ScoringMode.BindTo(scoringMode); - trackedUser.Score.BindValueChanged(_ => Scheduler.AddOnce(updateTotals)); + var scoreProcessor = new SpectatorScoreProcessor(user.UserID); + scoreProcessor.Mode.BindTo(scoringMode); + scoreProcessor.TotalScore.BindValueChanged(_ => Scheduler.AddOnce(updateTotals)); + AddInternal(scoreProcessor); + var trackedUser = new TrackedUserData(user, scoreProcessor); UserScores[user.UserID] = trackedUser; if (trackedUser.Team is int team && !TeamScores.ContainsKey(team)) TeamScores.Add(team, new BindableLong()); } - userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray()).ContinueWith(task => Schedule(() => - { - var users = task.GetResultSafely(); + userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray(), cancellationToken) + .ContinueWith(task => + { + Schedule(() => + { + var users = task.GetResultSafely(); - for (int i = 0; i < users.Length; i++) - { - var user = users[i] ?? new APIUser - { - Id = playingUsers[i].UserID, - Username = "Unknown user", - }; + for (int i = 0; i < users.Length; i++) + { + var user = users[i] ?? new APIUser + { + Id = playingUsers[i].UserID, + Username = "Unknown user", + }; - var trackedUser = UserScores[user.Id]; + var trackedUser = UserScores[user.Id]; - var leaderboardScore = Add(user, user.Id == api.LocalUser.Value.Id); - leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy); - leaderboardScore.TotalScore.BindTo(trackedUser.Score); - leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo); - leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit); - } - })); + var leaderboardScore = Add(user, user.Id == api.LocalUser.Value.Id); + leaderboardScore.Accuracy.BindTo(trackedUser.ScoreProcessor.Accuracy); + leaderboardScore.TotalScore.BindTo(trackedUser.ScoreProcessor.TotalScore); + leaderboardScore.Combo.BindTo(trackedUser.ScoreProcessor.Combo); + leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit); + } + }); + }, cancellationToken); } protected override void LoadComplete() @@ -118,20 +114,15 @@ namespace osu.Game.Screens.Play.HUD spectatorClient.WatchUser(user.UserID); if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(user.UserID)) - usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { user.UserID })); + playingUsersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { user.UserID })); } // bind here is to support players leaving the match. // new players are not supported. playingUserIds.BindTo(multiplayerClient.CurrentMatchPlayingUserIds); - playingUserIds.BindCollectionChanged(usersChanged); - - // this leaderboard should be guaranteed to be completely loaded before the gameplay starts (is a prerequisite in MultiplayerPlayer). - spectatorClient.OnNewFrames += handleIncomingFrames; + playingUserIds.BindCollectionChanged(playingUsersChanged); } - protected virtual TrackedUserData CreateUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) => new TrackedUserData(user, ruleset, scoreProcessor); - protected override GameplayLeaderboardScore CreateLeaderboardScoreDrawable(APIUser user, bool isTracked) { var leaderboardScore = base.CreateLeaderboardScoreDrawable(user, isTracked); @@ -157,7 +148,7 @@ namespace osu.Game.Screens.Play.HUD } } - private void usersChanged(object sender, NotifyCollectionChangedEventArgs e) + private void playingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { @@ -174,15 +165,6 @@ namespace osu.Game.Screens.Play.HUD } } - private void handleIncomingFrames(int userId, FrameDataBundle bundle) => Schedule(() => - { - if (!UserScores.TryGetValue(userId, out var trackedData)) - return; - - trackedData.Frames.Add(new TimedFrame(bundle.Frames.First().Time, bundle.Header)); - trackedData.UpdateScore(); - }); - private void updateTotals() { if (!hasTeams) @@ -196,7 +178,7 @@ namespace osu.Game.Screens.Play.HUD continue; if (TeamScores.TryGetValue(u.Team.Value, out var team)) - team.Value += (int)Math.Round(u.Score.Value); + team.Value += (int)Math.Round(u.ScoreProcessor.TotalScore.Value); } } @@ -207,83 +189,26 @@ namespace osu.Game.Screens.Play.HUD if (spectatorClient != null) { foreach (var user in playingUsers) - { spectatorClient.StopWatchingUser(user.UserID); - } - - spectatorClient.OnNewFrames -= handleIncomingFrames; } } protected class TrackedUserData { public readonly MultiplayerRoomUser User; - public readonly ScoreProcessor ScoreProcessor; + public readonly SpectatorScoreProcessor ScoreProcessor; - public readonly BindableDouble Score = new BindableDouble(); - public readonly BindableDouble Accuracy = new BindableDouble(1); - public readonly BindableInt CurrentCombo = new BindableInt(); public readonly BindableBool UserQuit = new BindableBool(); - public readonly IBindable ScoringMode = new Bindable(); - - public readonly List Frames = new List(); - public int? Team => (User.MatchState as TeamVersusUserState)?.TeamID; - private readonly ScoreInfo scoreInfo; - - public TrackedUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) + public TrackedUserData(MultiplayerRoomUser user, SpectatorScoreProcessor scoreProcessor) { User = user; ScoreProcessor = scoreProcessor; - - scoreInfo = new ScoreInfo { Ruleset = ruleset }; - - ScoringMode.BindValueChanged(_ => UpdateScore()); } public void MarkUserQuit() => UserQuit.Value = true; - - public virtual void UpdateScore() - { - if (Frames.Count == 0) - return; - - SetFrame(Frames.Last()); - } - - protected void SetFrame(TimedFrame frame) - { - var header = frame.Header; - - scoreInfo.MaxCombo = header.MaxCombo; - scoreInfo.Statistics = header.Statistics; - - Score.Value = ScoreProcessor.ComputePartialScore(ScoringMode.Value, scoreInfo); - - Accuracy.Value = header.Accuracy; - CurrentCombo.Value = header.Combo; - } - } - - protected class TimedFrame : IComparable - { - public readonly double Time; - public readonly FrameHeader Header; - - public TimedFrame(double time) - { - Time = time; - } - - public TimedFrame(double time, FrameHeader header) - { - Time = time; - Header = header; - } - - public int CompareTo(TimedFrame other) => Time.CompareTo(other.Time); } } } From 0d7fa3b55cd0b7e1822b1d73e42969ee3e4d5be6 Mon Sep 17 00:00:00 2001 From: tornac1234 Date: Mon, 30 May 2022 22:38:47 +0200 Subject: [PATCH 12/75] Added rulesets rankings updating for Discord RPC --- osu.Desktop/DiscordRichPresence.cs | 57 ++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index d87b25a4c7..9f3a5b2f07 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Text; using DiscordRPC; using DiscordRPC.Message; @@ -12,6 +13,7 @@ using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Users; @@ -30,6 +32,9 @@ namespace osu.Desktop private IBindable user; + [Resolved] + private IAPIProvider api { get; set; } + private readonly IBindable status = new Bindable(); private readonly IBindable activity = new Bindable(); @@ -66,7 +71,7 @@ namespace osu.Desktop activity.BindTo(u.NewValue.Activity); }, true); - ruleset.BindValueChanged(_ => updateStatus()); + ruleset.BindValueChanged(_ => requestUserRulesetsRankings()); status.BindValueChanged(_ => updateStatus()); activity.BindValueChanged(_ => updateStatus()); privacyMode.BindValueChanged(_ => updateStatus()); @@ -77,7 +82,7 @@ namespace osu.Desktop private void onReady(object _, ReadyMessage __) { Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug); - updateStatus(); + requestUserRulesetsRankings(); } private void updateStatus() @@ -106,7 +111,12 @@ namespace osu.Desktop if (privacyMode.Value == DiscordRichPresenceMode.Limited) presence.Assets.LargeImageText = string.Empty; else - presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); + { + if (user.Value.RulesetsStatistics != null && user.Value.RulesetsStatistics.TryGetValue(ruleset.Value.ShortName, out UserStatistics statistics)) + presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty); + else + presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); + } // update ruleset presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; @@ -115,6 +125,47 @@ namespace osu.Desktop client.SetPresence(presence); } + private void requestUserRulesetsRankings() + { + RulesetInfo cachedRuleset = ruleset.Value; + + // When first logging in, we can't send a request with an empty id + if (api.LocalUser.Value.Id > 1) + { + var req = new GetUsersRequest(new int[] { api.LocalUser.Value.Id }); + req.Success += result => + { + if (result.Users.Count == 1) + { + APIUser apiUser = result.Users[0]; + user.Value.RulesetsStatistics = apiUser.RulesetsStatistics; + } + + updateStatus(); + }; + req.Failure += _ => updateStatus(); + + api.Queue(req); + } + else + { + var req = new GetUserRequest(api.LocalUser.Value.Username, ruleset.Value); + + req.Success += result => + { + user.Value.RulesetsStatistics ??= new Dictionary(); + user.Value.RulesetsStatistics[ruleset.Value.ShortName] = result.Statistics; + updateStatus(); + }; + req.Failure += error => + { + updateStatus(); + }; + + api.Queue(req); + } + } + private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' }); private string truncate(string str) From a3f5e2458dd1a88d8448e157f2d751f9bcc7a2c6 Mon Sep 17 00:00:00 2001 From: tornac1234 Date: Mon, 30 May 2022 22:39:49 +0200 Subject: [PATCH 13/75] Show beatmap star rating in RPC --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 9f3a5b2f07..32c6931930 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -192,7 +192,7 @@ namespace osu.Desktop switch (activity) { case UserActivity.InGame game: - return game.BeatmapInfo.ToString(); + return $"[{Math.Round(game.BeatmapInfo.StarRating, 2)}*] {game.BeatmapInfo}"; case UserActivity.Editing edit: return edit.BeatmapInfo.ToString(); From bc1b20291bab23b2532b89985290b64e7fee169f Mon Sep 17 00:00:00 2001 From: tornac1234 Date: Mon, 30 May 2022 23:12:39 +0200 Subject: [PATCH 14/75] Revert "Show beatmap star rating in RPC" This reverts commit a3f5e2458dd1a88d8448e157f2d751f9bcc7a2c6. --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 32c6931930..9f3a5b2f07 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -192,7 +192,7 @@ namespace osu.Desktop switch (activity) { case UserActivity.InGame game: - return $"[{Math.Round(game.BeatmapInfo.StarRating, 2)}*] {game.BeatmapInfo}"; + return game.BeatmapInfo.ToString(); case UserActivity.Editing edit: return edit.BeatmapInfo.ToString(); From 80fa90f65d149d806a05a2e616f047616591ae04 Mon Sep 17 00:00:00 2001 From: tornac1234 Date: Mon, 30 May 2022 23:23:54 +0200 Subject: [PATCH 15/75] Removed a useless branch in the statistics request --- osu.Desktop/DiscordRichPresence.cs | 43 ++++++++---------------------- 1 file changed, 11 insertions(+), 32 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 9f3a5b2f07..44a3a1622c 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -71,7 +71,7 @@ namespace osu.Desktop activity.BindTo(u.NewValue.Activity); }, true); - ruleset.BindValueChanged(_ => requestUserRulesetsRankings()); + ruleset.BindValueChanged(_ => updateStatus()); status.BindValueChanged(_ => updateStatus()); activity.BindValueChanged(_ => updateStatus()); privacyMode.BindValueChanged(_ => updateStatus()); @@ -129,41 +129,20 @@ namespace osu.Desktop { RulesetInfo cachedRuleset = ruleset.Value; - // When first logging in, we can't send a request with an empty id - if (api.LocalUser.Value.Id > 1) + var req = new GetUsersRequest(new int[] { api.LocalUser.Value.Id }); + req.Success += result => { - var req = new GetUsersRequest(new int[] { api.LocalUser.Value.Id }); - req.Success += result => + if (result.Users.Count == 1) { - if (result.Users.Count == 1) - { - APIUser apiUser = result.Users[0]; - user.Value.RulesetsStatistics = apiUser.RulesetsStatistics; - } + APIUser apiUser = result.Users[0]; + user.Value.RulesetsStatistics = apiUser.RulesetsStatistics; + } - updateStatus(); - }; - req.Failure += _ => updateStatus(); + updateStatus(); + }; + req.Failure += _ => updateStatus(); - api.Queue(req); - } - else - { - var req = new GetUserRequest(api.LocalUser.Value.Username, ruleset.Value); - - req.Success += result => - { - user.Value.RulesetsStatistics ??= new Dictionary(); - user.Value.RulesetsStatistics[ruleset.Value.ShortName] = result.Statistics; - updateStatus(); - }; - req.Failure += error => - { - updateStatus(); - }; - - api.Queue(req); - } + api.Queue(req); } private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' }); From bcee9ac438cfbf755741279f3c2e9aaee19ec794 Mon Sep 17 00:00:00 2001 From: tornac1234 Date: Mon, 30 May 2022 23:32:55 +0200 Subject: [PATCH 16/75] Removed IAPIProvider from load and unified it's usage --- osu.Desktop/DiscordRichPresence.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 44a3a1622c..14fea7a08d 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Text; using DiscordRPC; using DiscordRPC.Message; @@ -33,7 +32,7 @@ namespace osu.Desktop private IBindable user; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider provider { get; set; } private readonly IBindable status = new Bindable(); private readonly IBindable activity = new Bindable(); @@ -46,7 +45,7 @@ namespace osu.Desktop }; [BackgroundDependencyLoader] - private void load(IAPIProvider provider, OsuConfigManager config) + private void load(OsuConfigManager config) { client = new DiscordRpcClient(client_id) { @@ -129,7 +128,7 @@ namespace osu.Desktop { RulesetInfo cachedRuleset = ruleset.Value; - var req = new GetUsersRequest(new int[] { api.LocalUser.Value.Id }); + var req = new GetUsersRequest(new int[] { provider.LocalUser.Value.Id }); req.Success += result => { if (result.Users.Count == 1) @@ -142,7 +141,7 @@ namespace osu.Desktop }; req.Failure += _ => updateStatus(); - api.Queue(req); + provider.Queue(req); } private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' }); From eaeb66547e3ceda671dcaa04850d247439893944 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 19:58:44 +0900 Subject: [PATCH 17/75] Revert comment removal --- .../Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index f4bfec169d..43b128b971 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -149,9 +149,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override void StartGameplay() { + // We can enter this screen one of two ways: + // 1. Via the automatic natural progression of PlayerLoader into Player. + // We'll arrive here in a Loaded state, and we need to let the server know that we're ready to start. + // 2. Via the server forcefully starting gameplay because players have been hanging out in PlayerLoader for too long. + // We'll arrive here in a Playing state, and we should neither show the loading spinner nor tell the server that we're ready to start (gameplay has already started). + // + // The base call is blocked here because in both cases gameplay is started only when the server says so via onGameplayStarted(). + if (client.LocalUser?.State == MultiplayerUserState.Loaded) { - // block base call, but let the server know we are ready to start. loadingDisplay.Show(); client.ChangeState(MultiplayerUserState.ReadyForGameplay); } From a434cc14a46eb7a4e7e301e12f90b60d1e220439 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Jun 2022 09:34:46 +0900 Subject: [PATCH 18/75] Apply code reviews --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 8e69ffa0a0..5712d9c94a 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; @@ -283,6 +284,7 @@ namespace osu.Game.Rulesets.Scoring /// The to represent the score as. /// The to compute the total score of. /// The total score in the given . + [Pure] public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo) { if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) @@ -303,6 +305,7 @@ namespace osu.Game.Rulesets.Scoring /// The to represent the score as. /// The to compute the total score of. /// The total score in the given . + [Pure] public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo) { if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) @@ -328,6 +331,7 @@ namespace osu.Game.Rulesets.Scoring /// The to compute the total score of. /// The maximum achievable combo for the provided beatmap. /// The total score in the given . + [Pure] public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo) { if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) @@ -354,6 +358,7 @@ namespace osu.Game.Rulesets.Scoring /// The current scoring values. /// The maximum scoring values. /// The total score computed from the given scoring values. + [Pure] public double ComputeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum) { double accuracyRatio = maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1; @@ -370,6 +375,7 @@ namespace osu.Game.Rulesets.Scoring /// The total bonus score. /// The total number of basic (non-tick and non-bonus) hitobjects in the beatmap. /// The total score computed from the given scoring component ratios. + [Pure] public double ComputeScore(ScoringMode mode, double accuracyRatio, double comboRatio, double bonusScore, int totalBasicHitObjects) { switch (mode) @@ -492,7 +498,8 @@ namespace osu.Game.Rulesets.Scoring /// The score to extract scoring values from. /// The "current" scoring values, representing the hit statistics as they appear. /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. - public void ExtractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum) + [Pure] + internal void ExtractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum) { extractScoringValues(scoreInfo.Statistics, out current, out maximum); current.MaxCombo = scoreInfo.MaxCombo; @@ -516,7 +523,8 @@ namespace osu.Game.Rulesets.Scoring /// The replay frame header to extract scoring values from. /// The "current" scoring values, representing the hit statistics as they appear. /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. - public void ExtractScoringValues(FrameHeader header, out ScoringValues current, out ScoringValues maximum) + [Pure] + internal void ExtractScoringValues(FrameHeader header, out ScoringValues current, out ScoringValues maximum) { extractScoringValues(header.Statistics, out current, out maximum); current.MaxCombo = header.MaxCombo; @@ -537,6 +545,7 @@ namespace osu.Game.Rulesets.Scoring /// The hit statistics to extract scoring values from. /// The "current" scoring values, representing the hit statistics as they appear. /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. + [Pure] private void extractScoringValues(IReadOnlyDictionary statistics, out ScoringValues current, out ScoringValues maximum) { current = default; From fc079ad8cfc1b458cbbf266bd39e8093b1ec91b2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Jun 2022 09:51:06 +0900 Subject: [PATCH 19/75] Refactor to reduce nested conditions --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 93 +++++++++------------ 1 file changed, 38 insertions(+), 55 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 5712d9c94a..26a32ab1a0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -168,43 +168,20 @@ namespace osu.Game.Rulesets.Scoring scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; + // Always update the maximum scoring values. + applyResult(result.Judgement.MaxResult, ref currentMaximumScoringValues); + currentMaximumScoringValues.MaxCombo += result.Judgement.MaxResult.IncreasesCombo() ? 1 : 0; + if (!result.Type.IsScorable()) - { - // The inverse of non-scorable (ignore) judgements may be bonus judgements. - if (result.Judgement.MaxResult.IsBonus()) - currentMaximumScoringValues.BonusScore += result.Judgement.MaxNumericResult; - return; - } - // Update rolling combo. if (result.Type.IncreasesCombo()) Combo.Value++; else if (result.Type.BreaksCombo()) Combo.Value = 0; - // Update maximum combo. + applyResult(result.Type, ref currentScoringValues); currentScoringValues.MaxCombo = HighestCombo.Value; - currentMaximumScoringValues.MaxCombo += result.Judgement.MaxResult.AffectsCombo() ? 1 : 0; - - // Update base/bonus score. - if (result.Type.IsBonus()) - { - currentScoringValues.BonusScore += result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - currentMaximumScoringValues.BonusScore += result.Judgement.MaxNumericResult; - } - else - { - currentScoringValues.BaseScore += result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - currentMaximumScoringValues.BaseScore += result.Judgement.MaxNumericResult; - } - - // Update hitobject count. - if (result.Type.IsBasic()) - { - currentScoringValues.HitObjects++; - currentMaximumScoringValues.HitObjects++; - } hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -212,6 +189,20 @@ namespace osu.Game.Rulesets.Scoring updateScore(); } + private static void applyResult(HitResult result, ref ScoringValues scoringValues) + { + if (!result.IsScorable()) + return; + + if (result.IsBonus()) + scoringValues.BonusScore += result.IsHit() ? Judgement.ToNumericResult(result) : 0; + else + scoringValues.BaseScore += result.IsHit() ? Judgement.ToNumericResult(result) : 0; + + if (result.IsBasic()) + scoringValues.HitObjects++; + } + /// /// Creates the that describes a . /// @@ -230,37 +221,15 @@ namespace osu.Game.Rulesets.Scoring scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; + // Always update the maximum scoring values. + revertResult(result.Judgement.MaxResult, ref currentMaximumScoringValues); + currentMaximumScoringValues.MaxCombo -= result.Judgement.MaxResult.IncreasesCombo() ? 1 : 0; + if (!result.Type.IsScorable()) - { - // The inverse of non-scorable (ignore) judgements may be bonus judgements. - if (result.Judgement.MaxResult.IsBonus()) - currentMaximumScoringValues.BonusScore -= result.Judgement.MaxNumericResult; - return; - } - // Update maximum combo. + revertResult(result.Type, ref currentScoringValues); currentScoringValues.MaxCombo = HighestCombo.Value; - currentMaximumScoringValues.MaxCombo -= result.Judgement.MaxResult.AffectsCombo() ? 1 : 0; - - // Update base/bonus score. - if (result.Type.IsBonus()) - { - currentScoringValues.BonusScore -= result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - currentMaximumScoringValues.BonusScore -= result.Judgement.MaxNumericResult; - } - else - { - currentScoringValues.BaseScore -= result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - currentMaximumScoringValues.BaseScore -= result.Judgement.MaxNumericResult; - } - - // Update hitobject count. - if (result.Type.IsBasic()) - { - currentScoringValues.HitObjects--; - currentMaximumScoringValues.HitObjects--; - } Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; @@ -269,6 +238,20 @@ namespace osu.Game.Rulesets.Scoring updateScore(); } + private static void revertResult(HitResult result, ref ScoringValues scoringValues) + { + if (!result.IsScorable()) + return; + + if (result.IsBonus()) + scoringValues.BonusScore -= result.IsHit() ? Judgement.ToNumericResult(result) : 0; + else + scoringValues.BaseScore -= result.IsHit() ? Judgement.ToNumericResult(result) : 0; + + if (result.IsBasic()) + scoringValues.HitObjects--; + } + private void updateScore() { Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; From caa29e1f9ecff4c86e973a7ad7309c482d13927d Mon Sep 17 00:00:00 2001 From: tornac1234 Date: Wed, 1 Jun 2022 12:05:24 +0200 Subject: [PATCH 20/75] Removed ruleset rankings request from DiscordRichPresence.cs --- osu.Desktop/DiscordRichPresence.cs | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 14fea7a08d..0d276d963f 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -12,7 +12,6 @@ using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Users; @@ -81,7 +80,7 @@ namespace osu.Desktop private void onReady(object _, ReadyMessage __) { Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug); - requestUserRulesetsRankings(); + updateStatus(); } private void updateStatus() @@ -124,26 +123,6 @@ namespace osu.Desktop client.SetPresence(presence); } - private void requestUserRulesetsRankings() - { - RulesetInfo cachedRuleset = ruleset.Value; - - var req = new GetUsersRequest(new int[] { provider.LocalUser.Value.Id }); - req.Success += result => - { - if (result.Users.Count == 1) - { - APIUser apiUser = result.Users[0]; - user.Value.RulesetsStatistics = apiUser.RulesetsStatistics; - } - - updateStatus(); - }; - req.Failure += _ => updateStatus(); - - provider.Queue(req); - } - private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' }); private string truncate(string str) From e2cdc66f6d378c20511c3be2fe724aa0f68a3eca Mon Sep 17 00:00:00 2001 From: tornac1234 Date: Wed, 1 Jun 2022 13:31:38 +0200 Subject: [PATCH 21/75] Added user RulesetsStatistics fetching when connecting --- osu.Game/Online/API/APIAccess.cs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 62ddd49881..f59bee1065 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -171,12 +171,32 @@ namespace osu.Game.Online.API }; userReq.Success += u => { - localUser.Value = u; - // todo: save/pull from settings - localUser.Value.Status.Value = new UserStatusOnline(); + u.Status.Value = new UserStatusOnline(); failureCount = 0; + + // getting user's full statistics (concerning every ruleset) + // we delay the localUser.Value setting because BindValueChanged won't record two value changes in a row + var statsRequest = new GetUsersRequest(new int[] { u.Id }); + statsRequest.Failure += _ => + { + localUser.Value = u; + failConnectionProcess(); + }; + statsRequest.Success += result => + { + if (result.Users.Count == 1) + { + u.RulesetsStatistics = result.Users[0].RulesetsStatistics; + localUser.Value = u; + return; + } + // Should never... happen ? + statsRequest.Fail(new Exception("Empty response for GetUsersRequest")); + }; + + handleRequest(statsRequest); }; if (!handleRequest(userReq)) From 600da89f72bcca5668b548896d5a3c9880deb1fd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 14:22:12 +0900 Subject: [PATCH 22/75] Fix disposing previous leaderboard too late --- .../Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index e94e198a4a..b115fa4ff2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Online.API.Requests.Responses; @@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("reset", () => { - Clear(); + leaderboard?.RemoveAndDisposeImmediately(); clocks = new Dictionary { From 793dfe2bc82f6dbff756b08bafe8c024fcee016b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 14:22:45 +0900 Subject: [PATCH 23/75] Mark players as playing --- .../Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index b115fa4ff2..60215dc8b3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -32,10 +32,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { PLAYER_2_ID, new ManualClock() } }; - foreach ((int userId, var _) in clocks) + foreach ((int userId, _) in clocks) { SpectatorClient.SendStartPlay(userId, 0); - OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = userId }); + OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = userId }, true); } }); From 6fb1fe06a03c56325f0f3d97fbbb8f3912ab79a1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 14:47:04 +0900 Subject: [PATCH 24/75] Remove unnecessary ApplyBeatmap call --- .../Spectator/SpectatorScoreProcessor.cs | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs index e81cf433a5..634af73c44 100644 --- a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs +++ b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs @@ -10,12 +10,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -109,10 +105,7 @@ namespace osu.Game.Online.Spectator spectatorState = userState; scoreInfo = new ScoreInfo { Ruleset = rulesetInfo }; scoreProcessor = ruleset.CreateScoreProcessor(); - - // Mods are required for score multiplier. scoreProcessor.Mods.Value = userState.Mods.Select(m => m.ToMod(ruleset)).ToArray(); - scoreProcessor.ApplyBeatmap(new DummyBeatmap()); } private void onNewFrames(int incomingUserId, FrameDataBundle bundle) @@ -165,23 +158,6 @@ namespace osu.Game.Online.Spectator spectatorClient.OnNewFrames -= onNewFrames; } - private class DummyBeatmap : IBeatmap - { - public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo(); - public BeatmapMetadata Metadata { get; } = new BeatmapMetadata(); - public BeatmapDifficulty Difficulty { get; set; } = new BeatmapDifficulty(); - public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo(); - public List Breaks { get; } = new List(); - public double TotalBreakTime => 0; - public IReadOnlyList HitObjects => Array.Empty(); - - public IEnumerable GetStatistics() => Array.Empty(); - - public double GetMostCommonBeatLength() => 0; - - public IBeatmap Clone() => throw new NotImplementedException(); - } - private class TimedFrame : IComparable { public readonly double Time; From 2209a009f9dc15f857a16e6580ce904bc796583a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 14:48:55 +0900 Subject: [PATCH 25/75] Don't process clock --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs index 589f5ce988..671f872188 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs @@ -20,6 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (!UserScores.TryGetValue(userId, out var data)) throw new ArgumentException(@"Provided user is not tracked by this leaderboard", nameof(userId)); + data.ScoreProcessor.ProcessCustomClock = false; data.ScoreProcessor.Clock = new FramedClock(clock); } From 4d9a77bdc06cc3ea368ff8865f8fdfc710c6098d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 15:02:44 +0900 Subject: [PATCH 26/75] Stop using Drawable.Clock altogether --- .../Online/Spectator/SpectatorScoreProcessor.cs | 14 +++++++++++++- .../Spectate/MultiSpectatorLeaderboard.cs | 3 +-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs index 634af73c44..c1829a7023 100644 --- a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs +++ b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs @@ -10,6 +10,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Timing; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -48,6 +49,17 @@ namespace osu.Game.Online.Spectator /// public IReadOnlyList Mods => scoreProcessor?.Mods.Value ?? Array.Empty(); + private IClock? referenceClock; + + /// + /// The clock used to determine the current score. + /// + public IClock ReferenceClock + { + get => referenceClock ?? Clock; + set => referenceClock = value; + } + [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; @@ -131,7 +143,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(spectatorState != null); Debug.Assert(scoreProcessor != null); - int frameIndex = replayFrames.BinarySearch(new TimedFrame(Time.Current)); + int frameIndex = replayFrames.BinarySearch(new TimedFrame(ReferenceClock.CurrentTime)); if (frameIndex < 0) frameIndex = ~frameIndex; frameIndex = Math.Clamp(frameIndex - 1, 0, replayFrames.Count - 1); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs index 671f872188..4e9ab07e4c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs @@ -20,8 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (!UserScores.TryGetValue(userId, out var data)) throw new ArgumentException(@"Provided user is not tracked by this leaderboard", nameof(userId)); - data.ScoreProcessor.ProcessCustomClock = false; - data.ScoreProcessor.Clock = new FramedClock(clock); + data.ScoreProcessor.ReferenceClock = clock; } protected override void Update() From 6f1437e73dcb62e2253d4285fc34c8b33f37e9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Jun 2022 21:15:11 +0200 Subject: [PATCH 27/75] Fix compilation failure due not applying rename fully --- .../Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs index 5e125c1a62..a738debecc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { BaseScore = 10000, MaxCombo = 1000, - HitObjects = 1000 + CountBasicHitObjects = 1000 } }; } From 524599568919c6b03c0fa3fc19f3ea69b77ab094 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Jun 2022 19:50:21 +0900 Subject: [PATCH 28/75] Use IsNotNull() helper --- osu.Game/Online/Spectator/SpectatorScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs index c1829a7023..a1e8715c8f 100644 --- a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs +++ b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Rulesets; @@ -165,8 +166,7 @@ namespace osu.Game.Online.Spectator { base.Dispose(isDisposing); - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (spectatorClient != null) + if (spectatorClient.IsNotNull()) spectatorClient.OnNewFrames -= onNewFrames; } From 68337df64377032faa9b86538164dacb30551fcf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Jun 2022 20:04:20 +0900 Subject: [PATCH 29/75] Fix tests by creating a score processor --- osu.Game/Tests/Gameplay/TestGameplayState.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Gameplay/TestGameplayState.cs b/osu.Game/Tests/Gameplay/TestGameplayState.cs index 0d00f52d15..f14f8c44ec 100644 --- a/osu.Game/Tests/Gameplay/TestGameplayState.cs +++ b/osu.Game/Tests/Gameplay/TestGameplayState.cs @@ -26,7 +26,10 @@ namespace osu.Game.Tests.Gameplay var workingBeatmap = new TestWorkingBeatmap(beatmap); var playableBeatmap = workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - return new GameplayState(playableBeatmap, ruleset, mods, score); + var scoreProcessor = ruleset.CreateScoreProcessor(); + scoreProcessor.ApplyBeatmap(playableBeatmap); + + return new GameplayState(playableBeatmap, ruleset, mods, score, scoreProcessor); } } } From 64616a6d73b3f074b2b133ff4acfce32c3297ee1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Jun 2022 00:18:54 +0900 Subject: [PATCH 30/75] Remove completely gimped implementation in `VolumeMeter` --- osu.Game/Overlays/Volume/VolumeMeter.cs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 929c362bd8..5dfcb94faf 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Framework.Utils; @@ -28,7 +27,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Volume { - public class VolumeMeter : Container, IKeyBindingHandler, IStateful + public class VolumeMeter : Container, IStateful { private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -365,27 +364,6 @@ namespace osu.Game.Overlays.Volume { } - public bool OnPressed(KeyBindingPressEvent e) - { - if (!IsHovered) - return false; - - switch (e.Action) - { - case GlobalAction.SelectPreviousGroup: - State = SelectionState.Selected; - adjust(1, false); - return true; - - case GlobalAction.SelectNextGroup: - State = SelectionState.Selected; - adjust(-1, false); - return true; - } - - return false; - } - public void OnReleased(KeyBindingReleaseEvent e) { } From 712253a35b74d2c5fb4c92bb86e298ebec8f2717 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Jun 2022 01:04:23 +0900 Subject: [PATCH 31/75] Make non-localisable strings in `VolumeMeter` verbatim --- osu.Game/Overlays/Volume/VolumeMeter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 5dfcb94faf..24d9f785f2 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Volume [BackgroundDependencyLoader] private void load(OsuColour colours, AudioManager audio) { - hoverSample = audio.Samples.Get($"UI/{HoverSampleSet.Button.GetDescription()}-hover"); + hoverSample = audio.Samples.Get($@"UI/{HoverSampleSet.Button.GetDescription()}-hover"); notchSample = audio.Samples.Get(@"UI/notch-tick"); sampleLastPlaybackTime = Time.Current; @@ -131,7 +131,7 @@ namespace osu.Game.Overlays.Volume { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Name = "Progress under covers for smoothing", + Name = @"Progress under covers for smoothing", RelativeSizeAxes = Axes.Both, Rotation = 180, Child = volumeCircle = new CircularProgress @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Volume }, new Circle { - Name = "Inner Cover", + Name = @"Inner Cover", Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, @@ -152,7 +152,7 @@ namespace osu.Game.Overlays.Volume }, new Container { - Name = "Progress overlay for glow", + Name = @"Progress overlay for glow", Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, From d0e098fbcdb02b597a28b07123605d7aef561205 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Jun 2022 01:04:46 +0900 Subject: [PATCH 32/75] Allow using arrow keys to navigate volume controls when controls are already visible --- osu.Game/Overlays/VolumeOverlay.cs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index a96949e96f..9d2ed3f837 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays.Volume; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays { @@ -171,6 +172,30 @@ namespace osu.Game.Overlays return base.OnMouseMove(e); } + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Left: + Adjust(GlobalAction.PreviousVolumeMeter); + return true; + + case Key.Right: + Adjust(GlobalAction.NextVolumeMeter); + return true; + + case Key.Down: + Adjust(GlobalAction.DecreaseVolume); + return true; + + case Key.Up: + Adjust(GlobalAction.IncreaseVolume); + return true; + } + + return base.OnKeyDown(e); + } + protected override bool OnHover(HoverEvent e) { schedulePopOut(); From c0aaeff2b3cfbf7ab75d17ba839af27165986afd Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Sat, 4 Jun 2022 16:11:49 +0100 Subject: [PATCH 33/75] Update `DaySeparator` to use new design throughout Moves `DaySeparator` chat component to it's own file and update it to match new chat design. Makes use of several virtual attributes that can be overridden to update spacing and layout in other usage contexts. Remove redundant usage of `ChatOverlayDaySeparator`, since the new design is now part of the base class. Create `StandAloneDaySeparator` to use in `StandAloneChatDisplay` which overrides attributes to match correct spacing and layout for its design. Ensure that `DrawableChannel.CreateDaySeparator` returns type of `DaySeparator` instead of `Drawable`. --- .../Online/TestSceneStandAloneChatDisplay.cs | 4 +- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 34 ++++-- .../Chat/ChatOverlayDrawableChannel.cs | 82 +------------- osu.Game/Overlays/Chat/DaySeparator.cs | 105 ++++++++++++++++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 80 +------------ 5 files changed, 138 insertions(+), 167 deletions(-) create mode 100644 osu.Game/Overlays/Chat/DaySeparator.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 860ef5d565..cb52f41c33 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -128,11 +128,11 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Ensure no adjacent day separators", () => { - var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds)); + var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds)); foreach (int i in indices) { - if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator) + if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DaySeparator) return false; } diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index b7e1bc999b..f57ffcfe25 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -155,9 +155,6 @@ namespace osu.Game.Online.Chat { public Func CreateChatLineAction; - [Resolved] - private OsuColour colours { get; set; } - public StandAloneDrawableChannel(Channel channel) : base(channel) { @@ -166,25 +163,40 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader] private void load() { + // TODO: Remove once DrawableChannel & ChatLine padding is fixed ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 }; } protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m); - protected override Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) + protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new StandAloneDaySeparator(time); + } + + protected class StandAloneDaySeparator : DaySeparator + { + protected override float TextSize => 14; + protected override float LineHeight => 1; + protected override float Spacing => 10; + protected override float DateAlign => 120; + + public StandAloneDaySeparator(DateTimeOffset time) + : base(time) { - TextSize = 14, - Colour = colours.Yellow, - LineHeight = 1, - Padding = new MarginPadding { Horizontal = 10 }, - Margin = new MarginPadding { Vertical = 5 }, - }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Height = 25; + Colour = colours.Yellow; + // TODO: Remove once DrawableChannel & ChatLine padding is fixed + Padding = new MarginPadding { Horizontal = 10 }; + } } protected class StandAloneMessage : ChatLine { protected override float TextSize => 15; - protected override float HorizontalPadding => 10; protected override float MessagePadding => 120; protected override float TimestampPadding => 50; diff --git a/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs b/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs index 3b47adc4b7..a5d22f678e 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs @@ -6,10 +6,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; namespace osu.Game.Overlays.Chat @@ -24,85 +20,19 @@ namespace osu.Game.Overlays.Chat [BackgroundDependencyLoader] private void load() { + // TODO: Remove once DrawableChannel & ChatLine padding is fixed ChatLineFlow.Padding = new MarginPadding(0); } - protected override Drawable CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time); + protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time); - private class ChatOverlayDaySeparator : Container + private class ChatOverlayDaySeparator : DaySeparator { - private readonly DateTimeOffset time; - public ChatOverlayDaySeparator(DateTimeOffset time) + : base(time) { - this.time = time; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Horizontal = 15, Vertical = 20 }; - Child = new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 200), - new Dimension(GridSizeMode.Absolute, 15), - new Dimension(), - }, - Content = new[] - { - new[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 15), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new[] - { - new Circle - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Colour = colourProvider.Background5, - RelativeSizeAxes = Axes.X, - Height = 2, - }, - Drawable.Empty(), - new OsuSpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(), - Font = OsuFont.Torus.With(size: 15, weight: FontWeight.SemiBold), - Colour = colourProvider.Content1, - }, - }, - }, - }, - Drawable.Empty(), - new Circle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = colourProvider.Background5, - RelativeSizeAxes = Axes.X, - Height = 2, - }, - }, - }, - }; + // TODO: Remove once DrawableChannel & ChatLine padding is fixed + Padding = new MarginPadding { Horizontal = 15 }; } } } diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs new file mode 100644 index 0000000000..2e3796151c --- /dev/null +++ b/osu.Game/Overlays/Chat/DaySeparator.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.Chat +{ + public class DaySeparator : Container + { + protected virtual float TextSize => 15; + + protected virtual float LineHeight => 2; + + protected virtual float DateAlign => 200; + + protected virtual float Spacing => 15; + + private readonly DateTimeOffset time; + + [Resolved(CanBeNull = true)] + private OverlayColourProvider? colourProvider { get; set; } + + public DaySeparator(DateTimeOffset time) + { + this.time = time; + Height = 40; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RowDimensions = new[] { new Dimension() }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, DateAlign), + new Dimension(GridSizeMode.Absolute, Spacing), + new Dimension(), + }, + Content = new[] + { + new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { new Dimension() }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, Spacing), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = LineHeight, + Colour = colourProvider?.Background5 ?? Colour4.White, + }, + Drawable.Empty(), + new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(), + Font = OsuFont.Torus.With(size: TextSize, weight: FontWeight.SemiBold), + Colour = colourProvider?.Content1 ?? Colour4.White, + }, + } + }, + }, + Drawable.Empty(), + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = LineHeight, + Colour = colourProvider?.Background5 ?? Colour4.White, + }, + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f2d4a3e301..f0d4d12f4f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -7,14 +7,9 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osuTK.Graphics; @@ -40,9 +35,6 @@ namespace osu.Game.Overlays.Chat } } - [Resolved] - private OsuColour colours { get; set; } - public DrawableChannel(Channel channel) { Channel = channel; @@ -67,7 +59,7 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Bottom = 5 }, Child = ChatLineFlow = new FillFlowContainer { - Padding = new MarginPadding { Left = 20, Right = 20 }, + Padding = new MarginPadding { Horizontal = 15 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, @@ -121,11 +113,7 @@ namespace osu.Game.Overlays.Chat protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m); - protected virtual Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) - { - Colour = colours.ChatBlue.Lighten(0.7f), - Margin = new MarginPadding { Vertical = 10 }, - }; + protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time); private void newMessagesArrived(IEnumerable newMessages) => Schedule(() => { @@ -203,69 +191,5 @@ namespace osu.Game.Overlays.Chat }); private IEnumerable chatLines => ChatLineFlow.Children.OfType(); - - public class DaySeparator : Container - { - public float TextSize - { - get => text.Font.Size; - set => text.Font = text.Font.With(size: value); - } - - private float lineHeight = 2; - - public float LineHeight - { - get => lineHeight; - set => lineHeight = leftBox.Height = rightBox.Height = value; - } - - private readonly SpriteText text; - private readonly Box leftBox; - private readonly Box rightBox; - - public DaySeparator(DateTimeOffset time) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Child = new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), }, - Content = new[] - { - new Drawable[] - { - leftBox = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = lineHeight, - }, - text = new OsuSpriteText - { - Margin = new MarginPadding { Horizontal = 10 }, - Text = time.ToLocalTime().ToString("dd MMM yyyy"), - }, - rightBox = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = lineHeight, - }, - } - } - }; - } - } } } From 6351f652a2eeaeb2128254638bb9e4143edb5f59 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 6 Jun 2022 17:56:25 +0900 Subject: [PATCH 34/75] Fix combo starting at 0 when spectating --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4a5897c621..df094ddb7c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -460,6 +460,7 @@ namespace osu.Game.Rulesets.Scoring currentMaximumScoringValues.BaseScore = maximum.BaseScore; currentMaximumScoringValues.MaxCombo = maximum.MaxCombo; + Combo.Value = frame.Header.Combo; HighestCombo.Value = frame.Header.MaxCombo; scoreResultCounts.Clear(); From 4e35ac8d4c5bba0c1577f9ab859b4d319c34dc7c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 6 Jun 2022 18:01:52 +0900 Subject: [PATCH 35/75] Add test --- osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index af4b002bc9..97be1dcfaa 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -59,12 +59,14 @@ namespace osu.Game.Tests.Gameplay scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great }); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1)); // No header shouldn't cause any change scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame()); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1)); // Reset with a miss instead. scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame @@ -74,6 +76,7 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); // Reset with no judged hit. scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame @@ -83,6 +86,7 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); Assert.That(scoreProcessor.JudgedHits, Is.Zero); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); } private class TestJudgement : Judgement From cd0e0fe70feda949ced24a7b4ba0e3f522001b40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jun 2022 18:02:42 +0900 Subject: [PATCH 36/75] Fix skin editor not accounting for aspect ratios in base-game sizing logic --- .../Screens/Edit/Components/EditorSidebar.cs | 4 ++- osu.Game/Skinning/Editor/SkinEditor.cs | 6 ++-- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 31 +++++++++++++++++-- .../Skinning/Editor/SkinEditorSceneLibrary.cs | 4 ++- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/EditorSidebar.cs b/osu.Game/Screens/Edit/Components/EditorSidebar.cs index 4edcef41b1..4e9b1d5222 100644 --- a/osu.Game/Screens/Edit/Components/EditorSidebar.cs +++ b/osu.Game/Screens/Edit/Components/EditorSidebar.cs @@ -16,13 +16,15 @@ namespace osu.Game.Screens.Edit.Components /// internal class EditorSidebar : Container { + public const float WIDTH = 250; + private readonly Box background; protected override Container Content { get; } public EditorSidebar() { - Width = 250; + Width = WIDTH; RelativeSizeAxes = Axes.Y; InternalChildren = new Drawable[] diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index e36d5ca3c6..30c33006ad 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -29,6 +29,8 @@ namespace osu.Game.Skinning.Editor { public const double TRANSITION_DURATION = 500; + public const float MENU_HEIGHT = 40; + public readonly BindableList SelectedComponents = new BindableList(); protected override bool StartHidden => true; @@ -78,8 +80,6 @@ namespace osu.Game.Skinning.Editor { RelativeSizeAxes = Axes.Both; - const float menu_height = 40; - InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -102,7 +102,7 @@ namespace osu.Game.Skinning.Editor Name = "Menu container", RelativeSizeAxes = Axes.X, Depth = float.MinValue, - Height = menu_height, + Height = MENU_HEIGHT, Children = new Drawable[] { new EditorMenuBar diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 497283a820..6b4b8abe1a 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -4,14 +4,17 @@ using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Layout; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Screens; +using osu.Game.Screens.Edit.Components; namespace osu.Game.Skinning.Editor { @@ -81,15 +84,37 @@ namespace osu.Game.Skinning.Editor protected override void PopOut() => skinEditor?.Hide(); + protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) + { + if (invalidation.HasFlagFast(Invalidation.DrawSize)) + Scheduler.AddOnce(updateScreenSizing); + + return base.OnInvalidate(invalidation, source); + } + + private void updateScreenSizing() + { + if (skinEditor?.State.Value != Visibility.Visible) return; + + float relativeSidebarWidth = EditorSidebar.WIDTH / DrawWidth; + float relativeToolbarHeight = (SkinEditorSceneLibrary.HEIGHT + SkinEditor.MENU_HEIGHT) / DrawHeight; + + var rect = new RectangleF( + relativeSidebarWidth, + relativeToolbarHeight, + 1 - relativeSidebarWidth * 2, + 1f - relativeToolbarHeight); + + scalingContainer.SetCustomRect(rect, true); + } + private void updateComponentVisibility() { Debug.Assert(skinEditor != null); - const float toolbar_padding_requirement = 0.18f; - if (skinEditor.State.Value == Visibility.Visible) { - scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.2f, 0.8f - toolbar_padding_requirement, 0.7f), true); + Scheduler.AddOnce(updateScreenSizing); game?.Toolbar.Hide(); game?.CloseAllOverlays(); diff --git a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs index 2124ba9b6d..dc5a8aefc0 100644 --- a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs @@ -27,6 +27,8 @@ namespace osu.Game.Skinning.Editor { public class SkinEditorSceneLibrary : CompositeDrawable { + public const float HEIGHT = BUTTON_HEIGHT + padding * 2; + public const float BUTTON_HEIGHT = 40; private const float padding = 10; @@ -42,7 +44,7 @@ namespace osu.Game.Skinning.Editor public SkinEditorSceneLibrary() { - Height = BUTTON_HEIGHT + padding * 2; + Height = HEIGHT; } [BackgroundDependencyLoader] From 003a3de270ad5585ce5bdbc02ef2d622bc8b4a84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jun 2022 18:06:46 +0900 Subject: [PATCH 37/75] Adjust transitions to look better --- osu.Game/Graphics/Containers/ScalingContainer.cs | 14 +++++++------- osu.Game/Skinning/Editor/SkinEditor.cs | 5 ++++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 11bfd80ec1..5c6e315225 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers /// public class ScalingContainer : Container { - private const float duration = 500; + internal const float TRANSITION_DURATION = 500; private Bindable sizeX; private Bindable sizeY; @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.Containers if (applyUIScale) { uiScale = osuConfig.GetBindable(OsuSetting.UIScale); - uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, duration, Easing.OutQuart), true); + uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, TRANSITION_DURATION, Easing.OutQuart), true); } } @@ -163,10 +163,10 @@ namespace osu.Game.Graphics.Containers backgroundStack.Push(new ScalingBackgroundScreen()); } - backgroundStack.FadeIn(duration); + backgroundStack.FadeIn(TRANSITION_DURATION); } else - backgroundStack?.FadeOut(duration); + backgroundStack?.FadeOut(TRANSITION_DURATION); } RectangleF targetRect = new RectangleF(Vector2.Zero, Vector2.One); @@ -195,13 +195,13 @@ namespace osu.Game.Graphics.Containers if (requiresMasking) sizableContainer.Masking = true; - sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart); - sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart); + sizableContainer.MoveTo(targetRect.Location, TRANSITION_DURATION, Easing.OutQuart); + sizableContainer.ResizeTo(targetRect.Size, TRANSITION_DURATION, Easing.OutQuart); // Of note, this will not work great in the case of nested ScalingContainers where multiple are applying corner radius. // Masking and corner radius should likely only be applied at one point in the full game stack to fix this. // An example of how this can occur is when the skin editor is visible and the game screen scaling is set to "Everything". - sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None) + sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, TRANSITION_DURATION, requiresMasking ? Easing.OutQuart : Easing.None) .OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 30c33006ad..095763de18 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -322,7 +322,10 @@ namespace osu.Game.Skinning.Editor protected override void PopIn() { - this.FadeIn(TRANSITION_DURATION, Easing.OutQuint); + this + // align animation to happen after the majority of the ScalingContainer animation completes. + .Delay(ScalingContainer.TRANSITION_DURATION * 0.3f) + .FadeIn(TRANSITION_DURATION, Easing.OutQuint); } protected override void PopOut() From 28c9c61f71e89d80528369718e02d4cbe7a5499f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jun 2022 18:26:52 +0900 Subject: [PATCH 38/75] Fix potential null reference in skin editor if target screen is null (during exit) ```csharp [runtime] 2022-06-06 09:24:31 [verbose]: Host execution state changed to Stopping [runtime] 2022-06-06 09:24:31 [error]: An unhandled error has occurred. [runtime] 2022-06-06 09:24:31 [error]: System.NullReferenceException: Object reference not set to an instance of an object. [runtime] 2022-06-06 09:24:31 [error]: at osu.Game.Skinning.Editor.SkinEditorOverlay.setTarget(OsuScreen target) in /Users/dean/Projects/osu/osu.Game/Skinning/Editor/SkinEditorOverlay.cs:line 173 [runtime] 2022-06-06 09:24:31 [error]: at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() [runtime] 2022-06-06 09:24:31 [error]: at osu.Framework.Threading.Scheduler.Update() [runtime] 2022-06-06 09:24:31 [error]: at osu.Framework.Graphics.Drawable.UpdateSubTree() [runtime] 2022-06-06 09:24:31 [error]: at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() [runtime] 2022-06-06 09:24:31 [error]: at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() [runtime] 2022-06-06 09:24:31 [error]: at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() [runtime] 2022-06-06 09:24:31 [error]: at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() ``` --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 497283a820..d4acfd1c4e 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -127,6 +127,9 @@ namespace osu.Game.Skinning.Editor private void setTarget(OsuScreen target) { + if (target == null) + return; + Debug.Assert(skinEditor != null); if (!target.IsLoaded) From a8764b67e1ac962cdbb78d97ddba6ab270167e78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jun 2022 18:27:41 +0900 Subject: [PATCH 39/75] Add padding and avoid using invalidation (triggers too often when toolbar is being toggled) --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 6b4b8abe1a..c96abe3e1e 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Screens; using osu.Game.Screens.Edit.Components; +using osuTK; namespace osu.Game.Skinning.Editor { @@ -36,6 +37,8 @@ namespace osu.Game.Skinning.Editor private OsuScreen lastTargetScreen; + private Vector2 lastDrawSize; + public SkinEditorOverlay(ScalingContainer scalingContainer) { this.scalingContainer = scalingContainer; @@ -92,18 +95,31 @@ namespace osu.Game.Skinning.Editor return base.OnInvalidate(invalidation, source); } + protected override void Update() + { + base.Update(); + + if (game.DrawSize != lastDrawSize) + { + lastDrawSize = game.DrawSize; + updateScreenSizing(); + } + } + private void updateScreenSizing() { if (skinEditor?.State.Value != Visibility.Visible) return; - float relativeSidebarWidth = EditorSidebar.WIDTH / DrawWidth; - float relativeToolbarHeight = (SkinEditorSceneLibrary.HEIGHT + SkinEditor.MENU_HEIGHT) / DrawHeight; + const float padding = 10; + + float relativeSidebarWidth = (EditorSidebar.WIDTH + padding) / DrawWidth; + float relativeToolbarHeight = (SkinEditorSceneLibrary.HEIGHT + SkinEditor.MENU_HEIGHT + padding) / DrawHeight; var rect = new RectangleF( relativeSidebarWidth, relativeToolbarHeight, 1 - relativeSidebarWidth * 2, - 1f - relativeToolbarHeight); + 1f - relativeToolbarHeight - padding / DrawHeight); scalingContainer.SetCustomRect(rect, true); } From 3862681d94d93b1a6642b15d2416d31102a8754b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jun 2022 19:43:08 +0900 Subject: [PATCH 40/75] Change `skin.ini` boolean parsing to match osu!stable Closes https://github.com/ppy/osu/issues/18579. --- osu.Game/Skinning/LegacySkin.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b65ba8b04c..9524d3f615 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -303,8 +303,13 @@ namespace osu.Game.Skinning if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out string val)) { // special case for handling skins which use 1 or 0 to signify a boolean state. + // ..or in some cases 2 (https://github.com/ppy/osu/issues/18579). if (typeof(TValue) == typeof(bool)) - val = val == "1" ? "true" : "false"; + { + val = bool.TryParse(val, out bool boolVal) + ? Convert.ChangeType(boolVal, typeof(bool)).ToString() + : Convert.ChangeType(Convert.ToInt32(val), typeof(bool)).ToString(); + } var bindable = new Bindable(); if (val != null) From da000ee5f0f28b162ae5f09054158e62123f03d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jun 2022 20:11:43 +0900 Subject: [PATCH 41/75] Centralise video file extensions --- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 ++-- osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 9d3ea7d192..541a78089e 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - private static readonly string[] video_extensions = { ".mp4", ".mov", ".avi", ".flv" }; + public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv" }; public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) : base(realm, storage, onlineLookupQueue) @@ -146,7 +146,7 @@ namespace osu.Game.Beatmaps notification.Text = $"Deleting videos from {HumanisedModelName}s ({++i} of {items.Count})"; - var video = b.Files.FirstOrDefault(f => video_extensions.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal))); + var video = b.Files.FirstOrDefault(f => VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal))); if (video != null) DeleteFile(b, video); diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs index 3d09d09833..d9dbf4974b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.IO; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -65,6 +67,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 { get { + if (BeatmapModelManager.VIDEO_EXTENSIONS.Contains(File.Extension)) + return FontAwesome.Regular.FileVideo; + switch (File.Extension) { case @".ogg": @@ -77,12 +82,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 case @".png": return FontAwesome.Regular.FileImage; - case @".mp4": - case @".avi": - case @".mov": - case @".flv": - return FontAwesome.Regular.FileVideo; - default: return FontAwesome.Regular.File; } From b104b7a90d3a50f3c89ac791a8d5bf316a686600 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jun 2022 20:12:18 +0900 Subject: [PATCH 42/75] Rename method to mention "all" --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- .../Overlays/Settings/Sections/Maintenance/GeneralSettings.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 92266924df..dba457c81c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,7 +319,7 @@ namespace osu.Game.Beatmaps }); } - public void DeleteVideos() + public void DeleteAllVideos() { realm.Write(r => { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 37758875ea..054de8dbd7 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => { deleteBeatmapVideosButton.Enabled.Value = false; - Task.Run(beatmaps.DeleteVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); + Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); })); } }); From f96340e37d3d852a67132175dd0bffc70d8b360e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jun 2022 20:18:32 +0900 Subject: [PATCH 43/75] Improve messaging of deletion progress / completion --- osu.Game/Beatmaps/BeatmapModelManager.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 541a78089e..ed64a43097 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -130,13 +130,15 @@ namespace osu.Game.Beatmaps { Progress = 0, Text = $"Preparing to delete all {HumanisedModelName} videos...", - CompletionText = $"Deleted all {HumanisedModelName} videos!", + CompletionText = $"No videos found to delete!", State = ProgressNotificationState.Active, }; + if (!silent) PostNotification?.Invoke(notification); int i = 0; + int deleted = 0; foreach (var b in items) { @@ -144,13 +146,18 @@ namespace osu.Game.Beatmaps // user requested abort return; - notification.Text = $"Deleting videos from {HumanisedModelName}s ({++i} of {items.Count})"; - var video = b.Files.FirstOrDefault(f => VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal))); - if (video != null) - DeleteFile(b, video); - notification.Progress = (float)i / items.Count; + if (video != null) + { + DeleteFile(b, video); + deleted++; + notification.CompletionText = $"Deleted {deleted} {HumanisedModelName} video(s)!"; + } + + notification.Text = $"Deleting videos from {HumanisedModelName}s ({deleted} deleted)"; + + notification.Progress = (float)++i / items.Count; } notification.State = ProgressNotificationState.Completed; From 3b4b35c51e3618e995058dde2494053d76e704f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jun 2022 20:18:57 +0900 Subject: [PATCH 44/75] Remove unnecessary string interpolation --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index ed64a43097..277047348e 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -130,7 +130,7 @@ namespace osu.Game.Beatmaps { Progress = 0, Text = $"Preparing to delete all {HumanisedModelName} videos...", - CompletionText = $"No videos found to delete!", + CompletionText = "No videos found to delete!", State = ProgressNotificationState.Active, }; From 211f0d1e04c5cad2d892dfae2086f1bb299db163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Jun 2022 19:57:08 +0200 Subject: [PATCH 45/75] Expand test coverage for parsing bool skin config values --- .../Skins/TestSceneSkinConfigurationLookup.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index d3cacaa88c..d68398236a 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -59,11 +59,13 @@ namespace osu.Game.Tests.Skins AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f); } - [Test] - public void TestBoolLookup() + [TestCase("0", false)] + [TestCase("1", true)] + [TestCase("2", true)] // https://github.com/ppy/osu/issues/18579 + public void TestBoolLookup(string originalValue, bool expectedParsedValue) { - AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = "1"); - AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = originalValue); + AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == expectedParsedValue); } [Test] From 0eaf420fa1d20c94b1b867f204294ddbff04a560 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Jun 2022 00:29:14 +0300 Subject: [PATCH 46/75] Specify full size for spinner ticks container --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a904658a4c..fa095edafa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -79,7 +79,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Result = { BindTarget = SpinsPerMinute }, }, - ticks = new Container(), + ticks = new Container + { + RelativeSizeAxes = Axes.Both, + }, new AspectContainer { Anchor = Anchor.Centre, From 4158146c719f6ef3d7f58991b284cf599426d8e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Jun 2022 00:29:53 +0300 Subject: [PATCH 47/75] Fix spinenr tick samples not positioned at centre Causing samples to be played at left ear rather than centre. --- .../Objects/Drawables/DrawableSpinnerTick.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 726fbd3ea6..39239c8233 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; + namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinnerTick : DrawableOsuHitObject @@ -10,13 +12,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject; public DrawableSpinnerTick() - : base(null) + : this(null) { } public DrawableSpinnerTick(SpinnerTick spinnerTick) : base(spinnerTick) { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; } protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration; From b6e97e699a9f411f07062228697713e9a61b5539 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Jun 2022 00:34:18 +0300 Subject: [PATCH 48/75] Remove unnecessary position specification --- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index ddee4d3ebd..1a130e96b3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Osu.Objects double startTime = StartTime + (float)(i + 1) / totalSpins * Duration; AddNested(i < SpinsRequired - ? new SpinnerTick { StartTime = startTime, Position = Position } - : new SpinnerBonusTick { StartTime = startTime, Position = Position }); + ? new SpinnerTick { StartTime = startTime } + : new SpinnerBonusTick { StartTime = startTime }); } } From 46eba86ad17f9721c57f66fbb7578b9b8e59695b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jun 2022 09:29:30 +0900 Subject: [PATCH 49/75] Remove unintended left-over invalidation code --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index c96abe3e1e..ffd94e0aad 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -4,13 +4,11 @@ using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Layout; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Screens; @@ -87,14 +85,6 @@ namespace osu.Game.Skinning.Editor protected override void PopOut() => skinEditor?.Hide(); - protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) - { - if (invalidation.HasFlagFast(Invalidation.DrawSize)) - Scheduler.AddOnce(updateScreenSizing); - - return base.OnInvalidate(invalidation, source); - } - protected override void Update() { base.Update(); From 0bfbfc6411afaf3315b151594832f4d62e518a6c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Jun 2022 11:51:24 +0900 Subject: [PATCH 50/75] Update package --- .config/dotnet-tools.json | 2 +- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 65ac05261a..cbd0231fdb 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -21,7 +21,7 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2022.417.0", + "version": "2022.607.0", "commands": [ "localisation" ] diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ca92d06aed..63b8cf4cb5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From f576d53aed47845b562360dd77c5d018d5a3cef2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Jun 2022 11:54:31 +0900 Subject: [PATCH 51/75] Update some unmatching strings --- osu.Game/Localisation/AudioSettingsStrings.cs | 6 +++--- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 4 ++-- osu.Game/Localisation/JoystickSettingsStrings.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index f298717c99..0f0f560df9 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -30,12 +30,12 @@ namespace osu.Game.Localisation public static LocalisableString OutputDevice => new TranslatableString(getKey(@"output_device"), @"Output device"); /// - /// "Master" + /// "Hitsound stereo separation" /// public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Hitsound stereo separation"); /// - /// "Level" + /// "Master" /// public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master"); @@ -69,6 +69,6 @@ namespace osu.Game.Localisation /// public static LocalisableString OffsetWizard => new TranslatableString(getKey(@"offset_wizard"), @"Offset wizard"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 586e29a432..82d03dbb5b 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -210,7 +210,7 @@ namespace osu.Game.Localisation public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface"); /// - /// "Toggle Mod Select" + /// "Toggle mod select" /// public static LocalisableString ToggleModSelection => new TranslatableString(getKey(@"toggle_mod_selection"), @"Toggle mod select"); @@ -299,6 +299,6 @@ namespace osu.Game.Localisation /// public static LocalisableString ToggleChatFocus => new TranslatableString(getKey(@"toggle_chat_focus"), @"Toggle chat focus"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/JoystickSettingsStrings.cs b/osu.Game/Localisation/JoystickSettingsStrings.cs index 410cd0a6f5..976ec1adde 100644 --- a/osu.Game/Localisation/JoystickSettingsStrings.cs +++ b/osu.Game/Localisation/JoystickSettingsStrings.cs @@ -15,10 +15,10 @@ namespace osu.Game.Localisation public static LocalisableString JoystickGamepad => new TranslatableString(getKey(@"joystick_gamepad"), @"Joystick / Gamepad"); /// - /// "Deadzone Threshold" + /// "Deadzone" /// public static LocalisableString DeadzoneThreshold => new TranslatableString(getKey(@"deadzone_threshold"), @"Deadzone"); private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} From ef5d601f6783ab6e5d0cc452e8a00f142132288e Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 7 Jun 2022 12:05:03 +0800 Subject: [PATCH 52/75] Fix difficulty name overflow in score panel --- .../Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs | 1 + osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 8b646df362..2a31728f87 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -96,6 +96,7 @@ namespace osu.Game.Tests.Visual.Ranking beatmap.Metadata.Author = author; beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title"; beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist"; + beatmap.DifficultyName = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong difficulty name"; return beatmap; } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5b3129dad6..b924fbd5df 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -159,6 +159,8 @@ namespace osu.Game.Screens.Ranking.Expanded Origin = Anchor.TopCentre, Text = beatmap.DifficultyName, Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold), + MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, + Truncate = true, }, new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12)) { From ac5b1fba1fc5993a61d60cbdcd7e48c20f6809c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jun 2022 15:36:26 +0900 Subject: [PATCH 53/75] Add blacklisted auto-import rules which conflict with osu!/osu!framework naming --- osu.sln.DotSettings | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 68cf8138e2..286a1eb29f 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -790,6 +790,15 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True True True True From df9174ec009fe71e873ea5712937fa85cd1a8a51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jun 2022 15:40:54 +0900 Subject: [PATCH 54/75] Remove import popup dialog from song select This has been replaced in spirit by the first run overlay. --- .../Navigation/TestSceneScreenNavigation.cs | 2 -- .../Screens/Select/ImportFromStablePopup.cs | 33 ------------------- osu.Game/Screens/Select/SongSelect.cs | 30 +++-------------- 3 files changed, 4 insertions(+), 61 deletions(-) delete mode 100644 osu.Game/Screens/Select/ImportFromStablePopup.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index c1f5f110d1..51bb27f93e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -609,8 +609,6 @@ namespace osu.Game.Tests.Visual.Navigation public ModSelectOverlay ModSelectOverlay => ModSelect; public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions; - - protected override bool DisplayStableImportPrompt => false; } } } diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs deleted file mode 100644 index d8137432bd..0000000000 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 osu.Framework.Graphics.Sprites; -using osu.Game.Overlays.Dialog; - -namespace osu.Game.Screens.Select -{ - public class ImportFromStablePopup : PopupDialog - { - public ImportFromStablePopup(Action importFromStable) - { - HeaderText = @"You have no beatmaps!"; - BodyText = "Would you like to import your beatmaps, skins, collections and scores from an existing osu!stable installation?\nThis will create a second copy of all files on disk."; - - Icon = FontAwesome.Solid.Plane; - - Buttons = new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = @"Yes please!", - Action = importFromStable - }, - new PopupDialogCancelButton - { - Text = @"No, I'd like to start from scratch", - }, - }; - } - } -} diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8870239485..242e741475 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -28,7 +28,6 @@ using osuTK.Input; using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; @@ -37,7 +36,6 @@ using osu.Game.Graphics.UserInterface; using System.Diagnostics; using JetBrains.Annotations; using osu.Game.Screens.Play; -using osu.Game.Database; using osu.Game.Skinning; namespace osu.Game.Screens.Select @@ -59,8 +57,6 @@ namespace osu.Game.Screens.Select protected virtual bool ShowFooter => true; - protected virtual bool DisplayStableImportPrompt => legacyImportManager?.SupportsImportFromStable == true; - public override bool? AllowTrackAdjustments => true; /// @@ -94,14 +90,13 @@ namespace osu.Game.Screens.Select protected Container LeftArea { get; private set; } private BeatmapInfoWedge beatmapInfoWedge; - private IDialogOverlay dialogOverlay; + + [Resolved] + private IDialogOverlay dialogOverlay { get; set; } [Resolved] private BeatmapManager beatmaps { get; set; } - [Resolved(CanBeNull = true)] - private LegacyImportManager legacyImportManager { get; set; } - protected ModSelectOverlay ModSelect { get; private set; } protected Sample SampleConfirm { get; private set; } @@ -127,7 +122,7 @@ namespace osu.Game.Screens.Select internal IOverlayManager OverlayManager { get; private set; } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, IDialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender) + private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender) { // initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter). transferRulesetValue(); @@ -289,26 +284,9 @@ namespace osu.Game.Screens.Select BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo)); } - dialogOverlay = dialog; - sampleChangeDifficulty = audio.Samples.Get(@"SongSelect/select-difficulty"); sampleChangeBeatmap = audio.Samples.Get(@"SongSelect/select-expand"); SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); - - if (dialogOverlay != null) - { - Schedule(() => - { - // if we have no beatmaps, let's prompt the user to import from over a stable install if he has one. - if (beatmaps.QueryBeatmapSet(s => !s.Protected && !s.DeletePending) == null && DisplayStableImportPrompt) - { - dialogOverlay.Push(new ImportFromStablePopup(() => - { - Task.Run(() => legacyImportManager.ImportFromStableAsync(StableContent.All)); - })); - } - }); - } } protected override void LoadComplete() From 0d32c94104d9da5b47d5b835d2cf91d6a0d077f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jun 2022 16:32:15 +0900 Subject: [PATCH 55/75] Add initial implementation of beatmap carousel no-results-placeholder --- .../SongSelect/TestScenePlaySongSelect.cs | 34 +++++ osu.Game/Screens/Select/BeatmapCarousel.cs | 15 ++- .../Screens/Select/NoResultsPlaceholder.cs | 122 ++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Select/NoResultsPlaceholder.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index aad7f6b301..77f5bd83d6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -18,6 +18,7 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; @@ -80,6 +81,37 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("delete all beatmaps", () => manager?.Delete()); } + [Test] + public void TestPlaceholderBeatmapPresence() + { + createSongSelect(); + + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); + + addRulesetImportStep(0); + AddUntilStep("wait for placeholder hidden", () => getPlaceholder()?.State.Value == Visibility.Hidden); + + AddStep("delete all beatmaps", () => manager?.Delete()); + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); + } + + [Test] + public void TestPlaceholderConvertSetting() + { + changeRuleset(2); + addRulesetImportStep(0); + AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); + + createSongSelect(); + + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); + + AddStep("click link in placeholder", () => getPlaceholder().ChildrenOfType().First().TriggerClick()); + + AddUntilStep("convert setting changed", () => config.Get(OsuSetting.ShowConvertedBeatmaps)); + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Hidden); + } + [Test] public void TestSingleFilterOnEnter() { @@ -941,6 +973,8 @@ namespace osu.Game.Tests.Visual.SongSelect private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.IndexOf(info); + private NoResultsPlaceholder getPlaceholder() => songSelect.ChildrenOfType().FirstOrDefault(); + private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmapInfo); private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a59f14647d..b7d253d7de 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -97,6 +97,8 @@ namespace osu.Game.Screens.Select protected readonly CarouselScrollContainer Scroll; + private readonly NoResultsPlaceholder noResultsPlaceholder; + private IEnumerable beatmapSets => root.Children.OfType(); // todo: only used for testing, maybe remove. @@ -170,7 +172,8 @@ namespace osu.Game.Screens.Select Scroll = new CarouselScrollContainer { RelativeSizeAxes = Axes.Both, - } + }, + noResultsPlaceholder = new NoResultsPlaceholder() } }; } @@ -648,8 +651,18 @@ namespace osu.Game.Screens.Select // First we iterate over all non-filtered carousel items and populate their // vertical position data. if (revalidateItems) + { updateYPositions(); + if (visibleItems.Count == 0) + { + noResultsPlaceholder.Filter = activeCriteria; + noResultsPlaceholder.Show(); + } + else + noResultsPlaceholder.Hide(); + } + // if there is a pending scroll action we apply it without animation and transfer the difference in position to the panels. // this is intentionally applied before updating the visible range below, to avoid animating new items (sourced from pool) from locations off-screen, as it looks bad. if (pendingScrollOperation != PendingScrollOperation.None) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs new file mode 100644 index 0000000000..0188dbd7db --- /dev/null +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -0,0 +1,122 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Select +{ + public class NoResultsPlaceholder : CompositeDrawable + { + private FilterCriteria filter; + + private LinkFlowContainer textFlow; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [Resolved(CanBeNull = true)] + private FirstRunSetupOverlay firstRunSetupOverlay { get; set; } + + public FilterCriteria Filter + { + get => filter; + set + { + filter = value; + Scheduler.AddOnce(updateText); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Masking = true; + CornerRadius = 10; + + Width = 300; + AutoSizeAxes = Axes.Y; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray2, + RelativeSizeAxes = Axes.Both, + }, + new SpriteIcon + { + Icon = FontAwesome.Regular.QuestionCircle, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding(10), + Size = new Vector2(50), + }, + textFlow = new LinkFlowContainer + { + Y = 70, + Padding = new MarginPadding(10), + TextAnchor = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + }; + } + + public override void Show() + { + this.FadeIn(600, Easing.OutQuint); + + this.ScaleTo(0.8f) + .ScaleTo(1f, 1000, Easing.OutElastic); + + Scheduler.AddOnce(updateText); + } + + public override void Hide() + { + this.FadeOut(200, Easing.OutQuint); + } + + private void updateText() + { + textFlow.Clear(); + + if (beatmaps.QueryBeatmapSet(s => !s.Protected && !s.DeletePending) == null) + { + textFlow.AddParagraph("No beatmaps found!"); + textFlow.AddParagraph(string.Empty); + + textFlow.AddParagraph("Consider running the "); + textFlow.AddLink("first run setup", () => firstRunSetupOverlay?.Show()); + textFlow.AddText(" to load or import some beatmaps!"); + } + else + { + textFlow.AddParagraph("No beatmaps match your filter criteria!"); + textFlow.AddParagraph(string.Empty); + + // TODO: hint when beatmaps are available in another ruleset + // TODO: hint when beatmaps are available by toggling "show converted". + if (!string.IsNullOrEmpty(filter?.SearchText)) + { + textFlow.AddParagraph("You can try "); + textFlow.AddLink("searching online", LinkAction.SearchBeatmapSet, filter.SearchText); + textFlow.AddText(" for this query."); + } + } + } + } +} From a04af1ca5f4ffb39b31b9ca3d33f47d5cec1393e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jun 2022 16:45:20 +0900 Subject: [PATCH 56/75] Enable nullable and add hinting at convert filter criteria --- .../Screens/Select/NoResultsPlaceholder.cs | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index 0188dbd7db..1b81deb30e 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; @@ -17,19 +20,21 @@ namespace osu.Game.Screens.Select { public class NoResultsPlaceholder : CompositeDrawable { - private FilterCriteria filter; + private FilterCriteria? filter; - private LinkFlowContainer textFlow; + private LinkFlowContainer textFlow = null!; [Resolved] - private BeatmapManager beatmaps { get; set; } + private BeatmapManager beatmaps { get; set; } = null!; - [Resolved(CanBeNull = true)] - private FirstRunSetupOverlay firstRunSetupOverlay { get; set; } + [Resolved] + private FirstRunSetupOverlay? firstRunSetupOverlay { get; set; } + + [Resolved] + private OsuConfigManager config { get; set; } = null!; public FilterCriteria Filter { - get => filter; set { filter = value; @@ -108,9 +113,18 @@ namespace osu.Game.Screens.Select textFlow.AddParagraph("No beatmaps match your filter criteria!"); textFlow.AddParagraph(string.Empty); - // TODO: hint when beatmaps are available in another ruleset - // TODO: hint when beatmaps are available by toggling "show converted". - if (!string.IsNullOrEmpty(filter?.SearchText)) + if (string.IsNullOrEmpty(filter?.SearchText)) + { + // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). + // TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting. + if (filter?.Ruleset.OnlineID > 0 && !filter.AllowConvertedBeatmaps) + { + textFlow.AddParagraph("Beatmaps may be available by "); + textFlow.AddLink("enabling automatic conversion", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); + textFlow.AddText("!"); + } + } + else { textFlow.AddParagraph("You can try "); textFlow.AddLink("searching online", LinkAction.SearchBeatmapSet, filter.SearchText); From f8524c3af4409d799b28a681b0ac3e35b25c87b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jun 2022 17:13:04 +0900 Subject: [PATCH 57/75] Use `VisibilityContainer` to avoid too many animations triggering --- .../Screens/Select/NoResultsPlaceholder.cs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index 1b81deb30e..28a0541a22 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -12,13 +12,14 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Localisation; using osu.Game.Online.Chat; using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Select { - public class NoResultsPlaceholder : CompositeDrawable + public class NoResultsPlaceholder : VisibilityContainer { private FilterCriteria? filter; @@ -37,6 +38,9 @@ namespace osu.Game.Screens.Select { set { + if (filter == value) + return; + filter = value; Scheduler.AddOnce(updateText); } @@ -63,7 +67,7 @@ namespace osu.Game.Screens.Select }, new SpriteIcon { - Icon = FontAwesome.Regular.QuestionCircle, + Icon = FontAwesome.Regular.SadTear, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Margin = new MarginPadding(10), @@ -71,7 +75,7 @@ namespace osu.Game.Screens.Select }, textFlow = new LinkFlowContainer { - Y = 70, + Y = 60, Padding = new MarginPadding(10), TextAnchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, @@ -80,23 +84,26 @@ namespace osu.Game.Screens.Select }; } - public override void Show() + protected override void PopIn() { this.FadeIn(600, Easing.OutQuint); - this.ScaleTo(0.8f) - .ScaleTo(1f, 1000, Easing.OutElastic); - Scheduler.AddOnce(updateText); } - public override void Hide() + protected override void PopOut() { this.FadeOut(200, Easing.OutQuint); } private void updateText() { + // TODO: Refresh this text when new beatmaps are imported. Right now it won't get up-to-date suggestions. + + // Bounce should play every time the filter criteria is updated. + this.ScaleTo(0.9f) + .ScaleTo(1f, 1000, Easing.OutElastic); + textFlow.Clear(); if (beatmaps.QueryBeatmapSet(s => !s.Protected && !s.DeletePending) == null) @@ -104,9 +111,9 @@ namespace osu.Game.Screens.Select textFlow.AddParagraph("No beatmaps found!"); textFlow.AddParagraph(string.Empty); - textFlow.AddParagraph("Consider running the "); - textFlow.AddLink("first run setup", () => firstRunSetupOverlay?.Show()); - textFlow.AddText(" to load or import some beatmaps!"); + textFlow.AddParagraph("Consider using the \""); + textFlow.AddLink(FirstRunSetupOverlayStrings.FirstRunSetupTitle, () => firstRunSetupOverlay?.Show()); + textFlow.AddText("\" to download or import some beatmaps!"); } else { @@ -131,6 +138,8 @@ namespace osu.Game.Screens.Select textFlow.AddText(" for this query."); } } + + // TODO: add clickable link to reset criteria. } } } From 8578f12a58a5b4043fa97b512686a227a90b4067 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Jun 2022 18:35:10 +0900 Subject: [PATCH 58/75] Fix taiko circle fills missing after rewind --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 504b10e9bc..2dd332fc13 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; +using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; @@ -321,12 +322,14 @@ namespace osu.Game.Rulesets.Taiko.UI private class ProxyContainer : LifetimeManagementContainer { - public new MarginPadding Padding - { - set => base.Padding = value; - } - public void Add(Drawable proxy) => AddInternal(proxy); + + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) + { + // DrawableHitObject disables masking. + // Hitobject content is proxied and unproxied based on hit status and the IsMaskedAway value could get stuck because of this. + return false; + } } } } From 2e0b8884105f977ed6cfc2d582a7aca1796bf004 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jun 2022 16:31:58 +0900 Subject: [PATCH 59/75] Fix song select carousel invalidating every frame during global overlay dimming --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a59f14647d..8a588c519b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Caching; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; @@ -633,7 +634,7 @@ namespace osu.Game.Screens.Select protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) { // handles the vertical size of the carousel changing (ie. on window resize when aspect ratio has changed). - if ((invalidation & Invalidation.Layout) > 0) + if (invalidation.HasFlagFast(Invalidation.DrawSize)) itemsCache.Invalidate(); return base.OnInvalidate(invalidation, source); From 228683e956217baaa211117af3d256fca8190303 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jun 2022 23:48:34 +0900 Subject: [PATCH 60/75] Fix nullability of `dialogOverlay` dependency --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 242e741475..3bfdc845ab 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select private BeatmapInfoWedge beatmapInfoWedge; - [Resolved] + [Resolved(canBeNull: true)] private IDialogOverlay dialogOverlay { get; set; } [Resolved] From 2f635fa8546bbd5efabb6ecb60b0c32792c32703 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 7 Jun 2022 01:37:46 +0100 Subject: [PATCH 61/75] Refactor `ChatLine` and fix `DrawableChannel` flow padding Refactors `ChatLine` component to use more sensible override properties and layout using grid container. Moves creation of username component into its own method to simplify BDL. Updates padding of base `DrawableChannel` flow padding. Removes usage of `ChatOverlayDrawableChannel` since it's overrides are no longer needed. Updates usage of `StandAloneChatDisplay` to use new override properties of `DrawableChannel`. --- .../Visual/Online/TestSceneChatOverlay.cs | 6 +- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 19 +- osu.Game/Overlays/Chat/ChatLine.cs | 192 +++++++++--------- .../Chat/ChatOverlayDrawableChannel.cs | 39 ---- osu.Game/Overlays/Chat/DaySeparator.cs | 2 +- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- osu.Game/Overlays/ChatOverlay.cs | 12 +- 7 files changed, 114 insertions(+), 158 deletions(-) delete mode 100644 osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 2cf1114f30..97221329f5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -572,15 +572,15 @@ namespace osu.Game.Tests.Visual.Online public SlowLoadingDrawableChannel GetSlowLoadingChannel(Channel channel) => DrawableChannels.OfType().Single(c => c.Channel == channel); - protected override ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel) + protected override DrawableChannel CreateDrawableChannel(Channel newChannel) { return SlowLoading ? new SlowLoadingDrawableChannel(newChannel) - : new ChatOverlayDrawableChannel(newChannel); + : new DrawableChannel(newChannel); } } - private class SlowLoadingDrawableChannel : ChatOverlayDrawableChannel + private class SlowLoadingDrawableChannel : DrawableChannel { public readonly ManualResetEventSlim LoadEvent = new ManualResetEventSlim(); diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index f57ffcfe25..bbfffea6fd 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -160,13 +160,6 @@ namespace osu.Game.Online.Chat { } - [BackgroundDependencyLoader] - private void load() - { - // TODO: Remove once DrawableChannel & ChatLine padding is fixed - ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 }; - } - protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m); protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new StandAloneDaySeparator(time); @@ -176,8 +169,8 @@ namespace osu.Game.Online.Chat { protected override float TextSize => 14; protected override float LineHeight => 1; - protected override float Spacing => 10; - protected override float DateAlign => 120; + protected override float Spacing => 5; + protected override float DateAlign => 125; public StandAloneDaySeparator(DateTimeOffset time) : base(time) @@ -189,17 +182,15 @@ namespace osu.Game.Online.Chat { Height = 25; Colour = colours.Yellow; - // TODO: Remove once DrawableChannel & ChatLine padding is fixed - Padding = new MarginPadding { Horizontal = 10 }; } } protected class StandAloneMessage : ChatLine { protected override float TextSize => 15; - protected override float HorizontalPadding => 10; - protected override float MessagePadding => 120; - protected override float TimestampPadding => 50; + protected override float Spacing => 5; + protected override float TimestampWidth => 45; + protected override float UsernameWidth => 75; public StandAloneMessage(Message message) : base(message) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index a1d8cd5d38..13f66089d9 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -26,22 +26,14 @@ namespace osu.Game.Overlays.Chat { public class ChatLine : CompositeDrawable { - public const float LEFT_PADDING = default_message_padding + default_horizontal_padding * 2; - - private const float default_message_padding = 200; - - protected virtual float MessagePadding => default_message_padding; - - private const float default_timestamp_padding = 65; - - protected virtual float TimestampPadding => default_timestamp_padding; - - private const float default_horizontal_padding = 15; - - protected virtual float HorizontalPadding => default_horizontal_padding; - protected virtual float TextSize => 20; + protected virtual float Spacing => 15; + + protected virtual float TimestampWidth => 60; + + protected virtual float UsernameWidth => 130; + private Color4 usernameColour; private OsuSpriteText timestamp; @@ -49,7 +41,6 @@ namespace osu.Game.Overlays.Chat public ChatLine(Message message) { Message = message; - Padding = new MarginPadding { Left = HorizontalPadding, Right = HorizontalPadding }; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; } @@ -90,88 +81,45 @@ namespace osu.Game.Overlays.Chat ? Color4Extensions.FromHex(message.Sender.Colour) : username_colours[message.Sender.Id % username_colours.Length]; - Drawable effectedUsername = username = new OsuSpriteText + InternalChild = new GridContainer { - Shadow = false, - Colour = senderHasColour ? colours.ChatBlue : usernameColour, - Truncate = true, - EllipsisString = "… :", - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - MaxWidth = MessagePadding - TimestampPadding - }; - - if (senderHasColour) - { - // Background effect - effectedUsername = new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + ColumnDimensions = new[] { - AutoSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 4, - EdgeEffect = new EdgeEffectParameters - { - Roundness = 1, - Radius = 1, - Colour = Color4.Black.Opacity(0.3f), - Offset = new Vector2(0, 1), - Type = EdgeEffectType.Shadow, - }, - Child = new Container - { - AutoSizeAxes = Axes.Both, - Y = 0, - Masking = true, - CornerRadius = 4, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = usernameColour, - }, - new Container - { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 4, Right = 4, Bottom = 1, Top = -2 }, - Child = username - } - } - } - }; - } - - InternalChildren = new Drawable[] - { - new Container - { - Size = new Vector2(MessagePadding, TextSize), - Children = new Drawable[] - { - timestamp = new OsuSpriteText - { - Shadow = false, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true) - }, - new MessageSender(message.Sender) - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Child = effectedUsername, - }, - } + new Dimension(GridSizeMode.Absolute, TimestampWidth + Spacing + UsernameWidth + Spacing), + new Dimension(), }, - new Container + Content = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = MessagePadding + HorizontalPadding }, - Children = new Drawable[] + new Drawable[] { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + timestamp = new OsuSpriteText + { + Shadow = false, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true), + MaxWidth = TimestampWidth, + }, + new MessageSender(message.Sender) + { + Width = UsernameWidth, + AutoSizeAxes = Axes.Y, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Child = createUsername(), + Margin = new MarginPadding { Horizontal = Spacing }, + }, + }, + }, ContentFlow = new LinkFlowContainer(t => { t.Shadow = false; @@ -190,7 +138,7 @@ namespace osu.Game.Overlays.Chat AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, } - } + }, } }; } @@ -233,7 +181,7 @@ namespace osu.Game.Overlays.Chat timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; - username.Text = $@"{message.Sender.Username}" + (senderHasColour || message.IsAction ? "" : ":"); + username.Text = $@"{message.Sender.Username}"; // remove non-existent channels from the link list message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true); @@ -242,6 +190,62 @@ namespace osu.Game.Overlays.Chat ContentFlow.AddLinks(message.DisplayContent, message.Links); } + private Drawable createUsername() + { + username = new OsuSpriteText + { + Shadow = false, + Colour = senderHasColour ? colours.ChatBlue : usernameColour, + Truncate = true, + EllipsisString = "…", + Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + MaxWidth = UsernameWidth, + }; + + if (!senderHasColour) + return username; + + // Background effect + return new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + EdgeEffect = new EdgeEffectParameters + { + Roundness = 1, + Radius = 1, + Colour = Color4.Black.Opacity(0.3f), + Offset = new Vector2(0, 1), + Type = EdgeEffectType.Shadow, + }, + Child = new Container + { + AutoSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = usernameColour, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 4, Right = 4, Bottom = 1, Top = -2 }, + Child = username + } + } + } + }; + } + private class MessageSender : OsuClickableContainer, IHasContextMenu { private readonly APIUser sender; diff --git a/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs b/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs deleted file mode 100644 index a5d22f678e..0000000000 --- a/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable enable - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Online.Chat; - -namespace osu.Game.Overlays.Chat -{ - public class ChatOverlayDrawableChannel : DrawableChannel - { - public ChatOverlayDrawableChannel(Channel channel) - : base(channel) - { - } - - [BackgroundDependencyLoader] - private void load() - { - // TODO: Remove once DrawableChannel & ChatLine padding is fixed - ChatLineFlow.Padding = new MarginPadding(0); - } - - protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time); - - private class ChatOverlayDaySeparator : DaySeparator - { - public ChatOverlayDaySeparator(DateTimeOffset time) - : base(time) - { - // TODO: Remove once DrawableChannel & ChatLine padding is fixed - Padding = new MarginPadding { Horizontal = 15 }; - } - } - } -} diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs index 2e3796151c..9ae35b0c38 100644 --- a/osu.Game/Overlays/Chat/DaySeparator.cs +++ b/osu.Game/Overlays/Chat/DaySeparator.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Chat protected virtual float LineHeight => 2; - protected virtual float DateAlign => 200; + protected virtual float DateAlign => 205; protected virtual float Spacing => 15; diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f0d4d12f4f..c3a341bca4 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Bottom = 5 }, Child = ChatLineFlow = new FillFlowContainer { - Padding = new MarginPadding { Horizontal = 15 }, + Padding = new MarginPadding { Horizontal = 10 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 02769b5d68..f04bf76c18 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -38,9 +38,9 @@ namespace osu.Game.Overlays private LoadingLayer loading = null!; private ChannelListing channelListing = null!; private ChatTextBar textBar = null!; - private Container currentChannelContainer = null!; + private Container currentChannelContainer = null!; - private readonly Dictionary loadedChannels = new Dictionary(); + private readonly Dictionary loadedChannels = new Dictionary(); protected IEnumerable DrawableChannels => loadedChannels.Values; @@ -126,7 +126,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4, }, - currentChannelContainer = new Container + currentChannelContainer = new Container { RelativeSizeAxes = Axes.Both, }, @@ -313,7 +313,7 @@ namespace osu.Game.Overlays loading.Show(); // Ensure the drawable channel is stored before async load to prevent double loading - ChatOverlayDrawableChannel drawableChannel = CreateDrawableChannel(newChannel); + DrawableChannel drawableChannel = CreateDrawableChannel(newChannel); loadedChannels.Add(newChannel, drawableChannel); LoadComponentAsync(drawableChannel, loadedDrawable => @@ -338,7 +338,7 @@ namespace osu.Game.Overlays channelManager.MarkChannelAsRead(newChannel); } - protected virtual ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel) => new ChatOverlayDrawableChannel(newChannel); + protected virtual DrawableChannel CreateDrawableChannel(Channel newChannel) => new DrawableChannel(newChannel); private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { @@ -361,7 +361,7 @@ namespace osu.Game.Overlays if (loadedChannels.ContainsKey(channel)) { - ChatOverlayDrawableChannel loaded = loadedChannels[channel]; + DrawableChannel loaded = loadedChannels[channel]; loadedChannels.Remove(channel); // DrawableChannel removed from cache must be manually disposed loaded.Dispose(); From 6c053291443ead7a3f9f0afe18085fe8c915d77f Mon Sep 17 00:00:00 2001 From: theangryepicbanana Date: Tue, 7 Jun 2022 18:01:40 -0400 Subject: [PATCH 62/75] Close #5820 (Ability to delete a single skin) --- osu.Game/Localisation/SkinSettingsStrings.cs | 5 ++ .../Overlays/Settings/Sections/SkinSection.cs | 34 +++++++++++++ osu.Game/Screens/Select/SkinDeleteDialog.cs | 48 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 osu.Game/Screens/Select/SkinDeleteDialog.cs diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs index 8b74b94d59..81035c5a5e 100644 --- a/osu.Game/Localisation/SkinSettingsStrings.cs +++ b/osu.Game/Localisation/SkinSettingsStrings.cs @@ -54,6 +54,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ExportSkinButton => new TranslatableString(getKey(@"export_skin_button"), @"Export selected skin"); + /// + /// "Delete selected skin" + /// + public static LocalisableString DeleteSkinButton => new TranslatableString(getKey(@"delete_skin_button"), @"Delete selected skin"); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index a87e65b735..e9713ad507 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -16,6 +16,7 @@ using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Screens.Select; using osu.Game.Skinning; using osu.Game.Skinning.Editor; using Realms; @@ -67,6 +68,7 @@ namespace osu.Game.Overlays.Settings.Sections Action = () => skinEditor?.ToggleVisibility(), }, new ExportSkinButton(), + new DeleteSkinButton(), }; config.BindWith(OsuSetting.Skin, configBindable); @@ -202,5 +204,37 @@ namespace osu.Game.Overlays.Settings.Sections } } } + + public class DeleteSkinButton : DangerousSettingsButton + { + [Resolved] + private SkinManager skins { get; set; } + + [Resolved(CanBeNull = true)] + private IDialogOverlay dialogOverlay { get; set; } + + private Bindable currentSkin; + + [BackgroundDependencyLoader] + private void load() + { + Text = SkinSettingsStrings.DeleteSkinButton; + Action = delete; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + currentSkin = skins.CurrentSkin.GetBoundCopy(); + currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.PerformRead(s => !s.Protected), true); + } + + private void delete() + { + if (dialogOverlay != null) + dialogOverlay.Push(new SkinDeleteDialog(currentSkin.Value)); + } + } } } diff --git a/osu.Game/Screens/Select/SkinDeleteDialog.cs b/osu.Game/Screens/Select/SkinDeleteDialog.cs new file mode 100644 index 0000000000..a971cca401 --- /dev/null +++ b/osu.Game/Screens/Select/SkinDeleteDialog.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Skinning; +using osu.Game.Overlays.Dialog; +using osu.Game.Database; + +namespace osu.Game.Screens.Select +{ + public class SkinDeleteDialog : PopupDialog + { + private SkinManager manager; + + [BackgroundDependencyLoader] + private void load(SkinManager skinManager) + { + manager = skinManager; + } + + public SkinDeleteDialog(Skin skin) + { + skin.SkinInfo.PerformRead(s => + { + BodyText = s.Name; + + Icon = FontAwesome.Regular.TrashAlt; + HeaderText = @"Confirm deletion of"; + Buttons = new PopupDialogButton[] + { + new PopupDialogDangerousButton + { + Text = @"Yes. Totally. Delete it.", + Action = () => { + manager.Delete(s); + manager.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged(); + }, + }, + new PopupDialogCancelButton + { + Text = @"Firetruck, I didn't mean to!", + }, + }; + }); + } + } +} From c69d53df0035ed1a469233cbbcd145ca706979ad Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Jun 2022 04:29:48 +0300 Subject: [PATCH 63/75] Add failing test case --- .../Visual/Online/TestSceneMessageNotifier.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 79f62a16e3..5f7c8b3c51 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -178,6 +179,36 @@ namespace osu.Game.Tests.Visual.Online AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1); } + /// + /// Ensures that handles channels which have not been or could not be resolved (i.e. = 0). + /// + [Test] + public void TestSendInUnresolvedChannel() + { + int i = 1; + Channel unresolved = null; + + AddRepeatStep("join unresolved channels", () => testContainer.ChannelManager.JoinChannel(unresolved = new Channel(new APIUser + { + Id = 100 + i, + Username = $"Foreign #{i++}", + })), 5); + + AddStep("send message in unresolved channel", () => + { + Debug.Assert(unresolved.Id == 0); + + unresolved.AddLocalEcho(new LocalEchoMessage + { + Sender = API.LocalUser.Value, + ChannelId = unresolved.Id, + Content = "Some message", + }); + }); + + AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0); + } + private void receiveMessage(APIUser sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content)); private Message createMessage(APIUser sender, Channel channel, string content) => new Message(messageIdCounter++) From 830ff666888e2d82303c38e6284553e1c169c698 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Jun 2022 04:31:31 +0300 Subject: [PATCH 64/75] Fix message notifier not handling unresolved PM channels --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index ca6082e19b..fbc5ef79ef 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -77,7 +77,7 @@ namespace osu.Game.Online.Chat if (!messages.Any()) return; - var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId); + var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id > 0 && c.Id == messages.First().ChannelId); if (channel == null) return; From 5157a78ae6f7fbb2c94482aeae8ac0b0e556bd2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Jun 2022 16:53:06 +0900 Subject: [PATCH 65/75] Isolate nested sample screens from main game to avoid toolbar interactions --- .../Overlays/FirstRunSetup/ScreenUIScale.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 8452691bb5..f09a26a527 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -126,6 +126,7 @@ namespace osu.Game.Overlays.FirstRunSetup private class SampleScreenContainer : CompositeDrawable { private readonly OsuScreen screen; + // Minimal isolation from main game. [Cached] @@ -151,6 +152,9 @@ namespace osu.Game.Overlays.FirstRunSetup RelativeSizeAxes = Axes.Both; } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => + new DependencyContainer(new DependencyIsolationContainer(base.CreateChildDependencies(parent))); + [BackgroundDependencyLoader] private void load(AudioManager audio, TextureStore textures, RulesetStore rulesets) { @@ -197,5 +201,41 @@ namespace osu.Game.Overlays.FirstRunSetup stack.PushSynchronously(screen); } } + + private class DependencyIsolationContainer : IReadOnlyDependencyContainer + { + private readonly IReadOnlyDependencyContainer parentDependencies; + + private readonly Type[] isolatedTypes = + { + typeof(OsuGame) + }; + + public DependencyIsolationContainer(IReadOnlyDependencyContainer parentDependencies) + { + this.parentDependencies = parentDependencies; + } + + public object Get(Type type) + { + if (isolatedTypes.Contains(type)) + return null; + + return parentDependencies.Get(type); + } + + public object Get(Type type, CacheInfo info) + { + if (isolatedTypes.Contains(type)) + return null; + + return parentDependencies.Get(type, info); + } + + public void Inject(T instance) where T : class + { + parentDependencies.Inject(instance); + } + } } } From 3a90aa0b9b9ccb909616a9c828c9c0fdaeb845a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Jun 2022 17:57:59 +0900 Subject: [PATCH 66/75] Fix code styling --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 3 +-- osu.Game/Screens/Select/SkinDeleteDialog.cs | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index e9713ad507..b83600a16d 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -232,8 +232,7 @@ namespace osu.Game.Overlays.Settings.Sections private void delete() { - if (dialogOverlay != null) - dialogOverlay.Push(new SkinDeleteDialog(currentSkin.Value)); + dialogOverlay?.Push(new SkinDeleteDialog(currentSkin.Value)); } } } diff --git a/osu.Game/Screens/Select/SkinDeleteDialog.cs b/osu.Game/Screens/Select/SkinDeleteDialog.cs index a971cca401..c7c36fb92c 100644 --- a/osu.Game/Screens/Select/SkinDeleteDialog.cs +++ b/osu.Game/Screens/Select/SkinDeleteDialog.cs @@ -32,7 +32,11 @@ namespace osu.Game.Screens.Select new PopupDialogDangerousButton { Text = @"Yes. Totally. Delete it.", - Action = () => { + Action = () => + { + if (manager == null) + return; + manager.Delete(s); manager.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged(); }, From f8594acb1d4018feb2e12af710f103be72580f15 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Jun 2022 18:10:41 +0900 Subject: [PATCH 67/75] Cleanup dialog implementation --- osu.Game/Screens/Select/SkinDeleteDialog.cs | 48 ++++++++------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Select/SkinDeleteDialog.cs b/osu.Game/Screens/Select/SkinDeleteDialog.cs index c7c36fb92c..4262118658 100644 --- a/osu.Game/Screens/Select/SkinDeleteDialog.cs +++ b/osu.Game/Screens/Select/SkinDeleteDialog.cs @@ -5,48 +5,38 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Game.Skinning; using osu.Game.Overlays.Dialog; -using osu.Game.Database; namespace osu.Game.Screens.Select { public class SkinDeleteDialog : PopupDialog { - private SkinManager manager; - - [BackgroundDependencyLoader] - private void load(SkinManager skinManager) - { - manager = skinManager; - } + [Resolved] + private SkinManager manager { get; set; } public SkinDeleteDialog(Skin skin) { - skin.SkinInfo.PerformRead(s => + BodyText = skin.SkinInfo.Value.Name; + Icon = FontAwesome.Regular.TrashAlt; + HeaderText = @"Confirm deletion of"; + Buttons = new PopupDialogButton[] { - BodyText = s.Name; - - Icon = FontAwesome.Regular.TrashAlt; - HeaderText = @"Confirm deletion of"; - Buttons = new PopupDialogButton[] + new PopupDialogDangerousButton { - new PopupDialogDangerousButton + Text = @"Yes. Totally. Delete it.", + Action = () => { - Text = @"Yes. Totally. Delete it.", - Action = () => - { - if (manager == null) - return; + if (manager == null) + return; - manager.Delete(s); - manager.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged(); - }, + manager.Delete(skin.SkinInfo.Value); + manager.CurrentSkinInfo.SetDefault(); }, - new PopupDialogCancelButton - { - Text = @"Firetruck, I didn't mean to!", - }, - }; - }); + }, + new PopupDialogCancelButton + { + Text = @"Firetruck, I didn't mean to!", + }, + }; } } } From c661f2b059e983938a4b2cbdb5f90e915968c1cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Jun 2022 18:54:23 +0900 Subject: [PATCH 68/75] Ensure `ChannelManager` has access to API from point of construction Closes https://github.com/ppy/osu/issues/18451. --- osu.Game.Tests/Chat/TestSceneChannelManager.cs | 8 ++++---- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 2 +- .../Visual/Online/TestSceneChatOverlay.cs | 2 +- .../Visual/Online/TestSceneMessageNotifier.cs | 7 ++++--- .../Online/TestSceneStandAloneChatDisplay.cs | 14 ++++++++++---- .../Components/TournamentMatchChatDisplay.cs | 5 +++-- osu.Game/Online/Chat/ChannelManager.cs | 6 +++--- osu.Game/OsuGame.cs | 2 +- 8 files changed, 27 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs index eaacc623c9..2bb6459f20 100644 --- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Chat [SetUp] public void Setup() => Schedule(() => { - var container = new ChannelManagerContainer(); + var container = new ChannelManagerContainer(API); Child = container; channelManager = container.ChannelManager; }); @@ -145,11 +145,11 @@ namespace osu.Game.Tests.Chat private class ChannelManagerContainer : CompositeDrawable { [Cached] - public ChannelManager ChannelManager { get; } = new ChannelManager(); + public ChannelManager ChannelManager { get; } - public ChannelManagerContainer() + public ChannelManagerContainer(IAPIProvider apiProvider) { - InternalChild = ChannelManager; + InternalChild = ChannelManager = new ChannelManager(apiProvider); } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index a28de3be1e..4d227af2cb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Online { linkColour = colours.Blue; - var chatManager = new ChannelManager(); + var chatManager = new ChannelManager(API); BindableList availableChannels = (BindableList)chatManager.AvailableChannels; availableChannels.Add(new Channel { Name = "#english" }); availableChannels.Add(new Channel { Name = "#japanese" }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 97221329f5..e3792c0780 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, CachedDependencies = new (Type, object)[] { - (typeof(ChannelManager), channelManager = new ChannelManager()), + (typeof(ChannelManager), channelManager = new ChannelManager(API)), }, Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 5f7c8b3c51..c7ca3b4457 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online Schedule(() => { - Child = testContainer = new TestContainer(new[] { publicChannel, privateMessageChannel }) + Child = testContainer = new TestContainer(API, new[] { publicChannel, privateMessageChannel }) { RelativeSizeAxes = Axes.Both, }; @@ -229,7 +229,7 @@ namespace osu.Game.Tests.Visual.Online private class TestContainer : Container { [Cached] - public ChannelManager ChannelManager { get; } = new ChannelManager(); + public ChannelManager ChannelManager { get; } [Cached(typeof(INotificationOverlay))] public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay @@ -245,9 +245,10 @@ namespace osu.Game.Tests.Visual.Online private readonly Channel[] channels; - public TestContainer(Channel[] channels) + public TestContainer(IAPIProvider api, Channel[] channels) { this.channels = channels; + ChannelManager = new ChannelManager(api); } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index cb52f41c33..8d5eebd31f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -11,6 +11,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Chat; using osuTK.Input; @@ -44,17 +45,22 @@ namespace osu.Game.Tests.Visual.Online Id = 5, }; - [Cached] - private ChannelManager channelManager = new ChannelManager(); + private ChannelManager channelManager; private TestStandAloneChatDisplay chatDisplay; private int messageIdSequence; private Channel testChannel; - public TestSceneStandAloneChatDisplay() + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - Add(channelManager); + Add(channelManager = new ChannelManager(parent.Get())); + + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.Cache(channelManager); + + return dependencies; } [SetUp] diff --git a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs index a5ead6c2f0..c30250c86a 100644 --- a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs @@ -5,6 +5,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat; using osu.Game.Tournament.IPC; @@ -29,7 +30,7 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader(true)] - private void load(MatchIPCInfo ipc) + private void load(MatchIPCInfo ipc, IAPIProvider api) { if (ipc != null) { @@ -45,7 +46,7 @@ namespace osu.Game.Tournament.Components if (manager == null) { - AddInternal(manager = new ChannelManager { HighPollRate = { Value = true } }); + AddInternal(manager = new ChannelManager(api) { HighPollRate = { Value = true } }); Channel.BindTo(manager.CurrentChannel); } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 31f67bcecc..c96e7c4cd3 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -61,8 +61,7 @@ namespace osu.Game.Online.Chat /// public IBindableList AvailableChannels => availableChannels; - [Resolved] - private IAPIProvider api { get; set; } + private readonly IAPIProvider api; [Resolved] private UserLookupCache users { get; set; } @@ -71,8 +70,9 @@ namespace osu.Game.Online.Chat private readonly IBindable isIdle = new BindableBool(); - public ChannelManager() + public ChannelManager(IAPIProvider api) { + this.api = api; CurrentChannel.ValueChanged += currentChannelChanged; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b91d523151..1f9a1ce938 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -851,7 +851,7 @@ namespace osu.Game loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true); - loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); + loadComponentSingleFile(channelManager = new ChannelManager(API), AddInternal, true); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); loadComponentSingleFile(new MessageNotifier(), AddInternal, true); loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true); From dfa31df2af366a5f3cf7e836a80d335151f3ba37 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 8 Jun 2022 13:42:30 +0100 Subject: [PATCH 69/75] Use `#nullable enable` in `ChatLine` --- osu.Game/Overlays/Chat/ChatLine.cs | 69 ++++++++++++++++-------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 13f66089d9..6cd0b76a49 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Linq; using System.Collections.Generic; @@ -26,33 +28,6 @@ namespace osu.Game.Overlays.Chat { public class ChatLine : CompositeDrawable { - protected virtual float TextSize => 20; - - protected virtual float Spacing => 15; - - protected virtual float TimestampWidth => 60; - - protected virtual float UsernameWidth => 130; - - private Color4 usernameColour; - - private OsuSpriteText timestamp; - - public ChatLine(Message message) - { - Message = message; - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - } - - [Resolved(CanBeNull = true)] - private ChannelManager chatManager { get; set; } - - private Message message; - private OsuSpriteText username; - - public LinkFlowContainer ContentFlow { get; private set; } - public Message Message { get => message; @@ -69,10 +44,40 @@ namespace osu.Game.Overlays.Chat } } + public LinkFlowContainer ContentFlow { get; private set; } = null!; + + protected virtual float TextSize => 20; + + protected virtual float Spacing => 15; + + protected virtual float TimestampWidth => 60; + + protected virtual float UsernameWidth => 130; + + private Color4 usernameColour; + + private OsuSpriteText timestamp = null!; + + private Message message = null!; + + private OsuSpriteText username = null!; + + private Container? highlight; + private bool senderHasColour => !string.IsNullOrEmpty(message.Sender.Colour); + [Resolved(CanBeNull = true)] + private ChannelManager? chatManager { get; set; } + [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; + + public ChatLine(Message message) + { + Message = message; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } [BackgroundDependencyLoader] private void load() @@ -151,8 +156,6 @@ namespace osu.Game.Overlays.Chat FinishTransforms(true); } - private Container highlight; - /// /// Performs a highlight animation on this . /// @@ -250,10 +253,10 @@ namespace osu.Game.Overlays.Chat { private readonly APIUser sender; - private Action startChatAction; + private Action startChatAction = null!; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; public MessageSender(APIUser sender) { @@ -261,7 +264,7 @@ namespace osu.Game.Overlays.Chat } [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile, ChannelManager chatManager) + private void load(UserProfileOverlay? profile, ChannelManager? chatManager) { Action = () => profile?.ShowUser(sender); startChatAction = () => chatManager?.OpenPrivateChannel(sender); From 04b434b8cee80b5d90a290caf766ea7e02287a06 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 8 Jun 2022 13:51:16 +0100 Subject: [PATCH 70/75] Update `ChatLine` timestamp and message colours --- osu.Game/Overlays/Chat/ChatLine.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 6cd0b76a49..a6ea4307ee 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -66,6 +66,8 @@ namespace osu.Game.Overlays.Chat private bool senderHasColour => !string.IsNullOrEmpty(message.Sender.Colour); + private bool messageHasColour => Message.IsAction && senderHasColour; + [Resolved(CanBeNull = true)] private ChannelManager? chatManager { get; set; } @@ -80,7 +82,7 @@ namespace osu.Game.Overlays.Chat } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider? colourProvider) { usernameColour = senderHasColour ? Color4Extensions.FromHex(message.Sender.Colour) @@ -113,6 +115,7 @@ namespace osu.Game.Overlays.Chat Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true), MaxWidth = TimestampWidth, + Colour = colourProvider?.Background1 ?? Colour4.White, }, new MessageSender(message.Sender) { @@ -128,16 +131,8 @@ namespace osu.Game.Overlays.Chat ContentFlow = new LinkFlowContainer(t => { t.Shadow = false; - - if (Message.IsAction) - { - t.Font = OsuFont.GetFont(italics: true); - - if (senderHasColour) - t.Colour = Color4Extensions.FromHex(message.Sender.Colour); - } - - t.Font = t.Font.With(size: TextSize); + t.Font = t.Font.With(size: TextSize, italics: Message.IsAction); + t.Colour = messageHasColour ? Color4Extensions.FromHex(message.Sender.Colour) : colourProvider?.Content1 ?? Colour4.White; }) { AutoSizeAxes = Axes.Y, From c2ed41d097f67659c684fa8cdb18984ff3db88b5 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 8 Jun 2022 15:10:19 +0100 Subject: [PATCH 71/75] Remove `CanBeNull` specification from DI attributes --- osu.Game/Overlays/Chat/ChatLine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index a6ea4307ee..56e39f212d 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Chat private bool messageHasColour => Message.IsAction && senderHasColour; - [Resolved(CanBeNull = true)] + [Resolved] private ChannelManager? chatManager { get; set; } [Resolved] @@ -258,7 +258,7 @@ namespace osu.Game.Overlays.Chat this.sender = sender; } - [BackgroundDependencyLoader(true)] + [BackgroundDependencyLoader] private void load(UserProfileOverlay? profile, ChannelManager? chatManager) { Action = () => profile?.ShowUser(sender); From bf67b35aded88dbe5a553ea480c36d04824e8fce Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Wed, 8 Jun 2022 17:44:57 +0300 Subject: [PATCH 72/75] Use new own profile statistics in difficulty recommender --- .../TestSceneBeatmapRecommendations.cs | 43 +++++-------- osu.Game/Beatmaps/DifficultyRecommender.cs | 64 +++++-------------- 2 files changed, 32 insertions(+), 75 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 940d001c5b..a78a8aa028 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -5,12 +5,11 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Extensions; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; @@ -23,38 +22,28 @@ namespace osu.Game.Tests.Visual.SongSelect { public class TestSceneBeatmapRecommendations : OsuGameTestScene { + [Resolved] + private IRulesetStore rulesetStore { get; set; } + [SetUpSteps] public override void SetUpSteps() { - AddStep("register request handling", () => - { - ((DummyAPIAccess)API).HandleRequest = req => - { - switch (req) - { - case GetUserRequest userRequest: - userRequest.TriggerSuccess(getUser(userRequest.Ruleset.OnlineID)); - return true; - } - - return false; - }; - }); - base.SetUpSteps(); - APIUser getUser(int? rulesetID) + AddStep("populate ruleset statistics", () => { - return new APIUser + Dictionary rulesetStatistics = new Dictionary(); + + rulesetStore.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo => { - Username = @"Dummy", - Id = 1001, - Statistics = new UserStatistics + rulesetStatistics[rulesetInfo.ShortName] = new UserStatistics { - PP = getNecessaryPP(rulesetID) - } - }; - } + PP = getNecessaryPP(rulesetInfo.OnlineID) + }; + }); + + API.LocalUser.Value.RulesetsStatistics = rulesetStatistics; + }); decimal getNecessaryPP(int? rulesetID) { diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 93c2fccbc7..72f1931c86 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -7,11 +7,8 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Game.Extensions; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Rulesets; namespace osu.Game.Beatmaps @@ -25,26 +22,15 @@ namespace osu.Game.Beatmaps [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private IRulesetStore rulesets { get; set; } - [Resolved] private Bindable ruleset { get; set; } - /// - /// The user for which the last requests were run. - /// - private int? requestedUserId; - - private readonly Dictionary recommendedDifficultyMapping = new Dictionary(); - - private readonly IBindable apiState = new Bindable(); + private readonly Dictionary recommendedDifficultyMapping = new Dictionary(); [BackgroundDependencyLoader] private void load() { - apiState.BindTo(api.State); - apiState.BindValueChanged(onlineStateChanged, true); + api.LocalUser.BindValueChanged(_ => populateValues(), true); } /// @@ -58,12 +44,12 @@ namespace osu.Game.Beatmaps [CanBeNull] public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps) { - foreach (var r in orderedRulesets) + foreach (string r in orderedRulesets) { if (!recommendedDifficultyMapping.TryGetValue(r, out double recommendation)) continue; - BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b => + BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.ShortName.Equals(r)).OrderBy(b => { double difference = b.StarRating - recommendation; return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder @@ -76,55 +62,37 @@ namespace osu.Game.Beatmaps return null; } - private void fetchRecommendedValues() + private void populateValues() { - if (recommendedDifficultyMapping.Count > 0 && api.LocalUser.Value.Id == requestedUserId) + if (api.LocalUser.Value.RulesetsStatistics == null) return; - requestedUserId = api.LocalUser.Value.Id; - - // only query API for built-in rulesets - rulesets.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo => + foreach (var statistic in api.LocalUser.Value.RulesetsStatistics) { - var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); - - req.Success += result => - { - // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 - recommendedDifficultyMapping[rulesetInfo] = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195; - }; - - api.Queue(req); - }); + decimal? pp = api.LocalUser.Value.RulesetsStatistics[statistic.Key].PP; + // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 + double recommended = Math.Pow((double)(pp ?? 0), 0.4) * 0.195; + recommendedDifficultyMapping[statistic.Key] = recommended; + } } /// /// Rulesets ordered descending by their respective recommended difficulties. /// The currently selected ruleset will always be first. /// - private IEnumerable orderedRulesets + private IEnumerable orderedRulesets { get { if (LoadState < LoadState.Ready || ruleset.Value == null) - return Enumerable.Empty(); + return Enumerable.Empty(); return recommendedDifficultyMapping .OrderByDescending(pair => pair.Value) .Select(pair => pair.Key) - .Where(r => !r.Equals(ruleset.Value)) - .Prepend(ruleset.Value); + .Where(r => !r.Equals(ruleset.Value.ShortName)) + .Prepend(ruleset.Value.ShortName); } } - - private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => - { - switch (state.NewValue) - { - case APIState.Online: - fetchRecommendedValues(); - break; - } - }); } } From eb33922417f451914a57d763b4a304a501309e63 Mon Sep 17 00:00:00 2001 From: tornac1234 Date: Wed, 8 Jun 2022 18:13:08 +0200 Subject: [PATCH 73/75] Revert "Added user RulesetsStatistics fetching when connecting" This reverts commit e2cdc66f6d378c20511c3be2fe724aa0f68a3eca. --- osu.Game/Online/API/APIAccess.cs | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index f59bee1065..62ddd49881 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -171,32 +171,12 @@ namespace osu.Game.Online.API }; userReq.Success += u => { + localUser.Value = u; + // todo: save/pull from settings - u.Status.Value = new UserStatusOnline(); + localUser.Value.Status.Value = new UserStatusOnline(); failureCount = 0; - - // getting user's full statistics (concerning every ruleset) - // we delay the localUser.Value setting because BindValueChanged won't record two value changes in a row - var statsRequest = new GetUsersRequest(new int[] { u.Id }); - statsRequest.Failure += _ => - { - localUser.Value = u; - failConnectionProcess(); - }; - statsRequest.Success += result => - { - if (result.Users.Count == 1) - { - u.RulesetsStatistics = result.Users[0].RulesetsStatistics; - localUser.Value = u; - return; - } - // Should never... happen ? - statsRequest.Fail(new Exception("Empty response for GetUsersRequest")); - }; - - handleRequest(statsRequest); }; if (!handleRequest(userReq)) From c90b2858619e6a03d58e192a8912de64646b827d Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Wed, 8 Jun 2022 21:10:27 +0300 Subject: [PATCH 74/75] Change variable name and inline it --- osu.Game/Beatmaps/DifficultyRecommender.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 72f1931c86..4629b20569 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -67,12 +67,10 @@ namespace osu.Game.Beatmaps if (api.LocalUser.Value.RulesetsStatistics == null) return; - foreach (var statistic in api.LocalUser.Value.RulesetsStatistics) + foreach (var kvp in api.LocalUser.Value.RulesetsStatistics) { - decimal? pp = api.LocalUser.Value.RulesetsStatistics[statistic.Key].PP; // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 - double recommended = Math.Pow((double)(pp ?? 0), 0.4) * 0.195; - recommendedDifficultyMapping[statistic.Key] = recommended; + recommendedDifficultyMapping[kvp.Key] = Math.Pow((double)(kvp.Value.PP ?? 0), 0.4) * 0.195; } } From 3799689c7dd6ac73560f8f24a483b3734a6505e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jun 2022 12:32:30 +0900 Subject: [PATCH 75/75] Tidy up variable naming and layout --- osu.Desktop/DiscordRichPresence.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 0d276d963f..bc5763a3b4 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -31,7 +31,7 @@ namespace osu.Desktop private IBindable user; [Resolved] - private IAPIProvider provider { get; set; } + private IAPIProvider api { get; set; } private readonly IBindable status = new Bindable(); private readonly IBindable activity = new Bindable(); @@ -60,7 +60,8 @@ namespace osu.Desktop config.BindWith(OsuSetting.DiscordRichPresence, privacyMode); - (user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u => + user = api.LocalUser.GetBoundCopy(); + user.BindValueChanged(u => { status.UnbindBindings(); status.BindTo(u.NewValue.Status);