From 40d20f4e1084480a35a60792dd98fbb69b84b5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Mar 2025 14:16:26 +0100 Subject: [PATCH 1/2] Allow tagging already played beatmaps without playing another time Addresses https://github.com/ppy/osu/discussions/32568#discussioncomment-12610577. No changes in criteria (yet?), just allowing locally imported plays to count the same way as full beatmap completion does. The test scene is a bit rough / semi-manual but dealing with score imports is a bit of a pain in general. The way to semi-manually test with the test scene is to import a subset of scores, then recreate the statistics panel, and observe behaviour. I'm not sure it's worth it to be putting subscriptions in there, so the full recreation of the panel is necessary. --- .../Ranking/TestSceneStatisticsPanel.cs | 135 +++++++++++++++--- .../Ranking/Statistics/StatisticsPanel.cs | 27 +++- 2 files changed, 141 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index f82b32167c..1749ea38b5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -8,11 +8,15 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -27,6 +31,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Rulesets.Osu.Objects; @@ -43,6 +48,22 @@ namespace osu.Game.Tests.Visual.Ranking { private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + private ScoreManager scoreManager = null!; + private RulesetStore rulesetStore = null!; + private BeatmapManager beatmapManager = null!; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API)); + Dependencies.Cache(Realm); + + return dependencies; + } + [Test] public void TestScoreWithPositionStatistics() { @@ -163,6 +184,24 @@ namespace osu.Game.Tests.Visual.Ranking { var score = TestResources.CreateTestScoreInfo(); + setUpTaggingRequests(() => score.BeatmapInfo); + AddStep("load panel", () => + { + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Child = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + Score = { Value = score }, + AchievedScore = score, + } + }; + }); + } + + private void setUpTaggingRequests(Func beatmap) => AddStep("set up network requests", () => { dummyAPI.HandleRequest = request => @@ -176,7 +215,11 @@ namespace osu.Game.Tests.Visual.Ranking Tags = [ new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", }, - new APITag { Id = 2, Name = "alt", Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", }, + new APITag + { + Id = 2, Name = "alt", + Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", + }, new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", }, new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", }, ] @@ -186,7 +229,7 @@ namespace osu.Game.Tests.Visual.Ranking case GetBeatmapSetRequest getBeatmapSetRequest: { - var beatmapSet = CreateAPIBeatmapSet(score.BeatmapInfo); + var beatmapSet = CreateAPIBeatmapSet(beatmap.Invoke()); beatmapSet.Beatmaps.Single().TopTags = [ new APIBeatmapTag { TagId = 3, VoteCount = 9 }, @@ -206,21 +249,6 @@ namespace osu.Game.Tests.Visual.Ranking return false; }; }); - AddStep("load panel", () => - { - Child = new PopoverContainer - { - RelativeSizeAxes = Axes.Both, - Child = new StatisticsPanel - { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible }, - Score = { Value = score }, - AchievedScore = score, - } - }; - }); - } [Test] public void TestTaggingWhenRankTooLow() @@ -266,6 +294,79 @@ namespace osu.Game.Tests.Visual.Ranking }); } + [Test] + public void TestTaggingInteractionWithLocalScores() + { + BeatmapInfo beatmapInfo = null!; + string originalHash = string.Empty; + + AddStep(@"Import beatmap", () => + { + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + }); + + AddStep("import bad score", () => + { + var score = TestResources.CreateTestScoreInfo(); + score.BeatmapInfo = beatmapInfo; + score.BeatmapHash = beatmapInfo.Hash; + score.Ruleset = beatmapInfo.Ruleset; + score.Rank = ScoreRank.D; + score.User = API.LocalUser.Value; + scoreManager.Import(score); + }); + + AddStep("import score by another user", () => + { + var score = TestResources.CreateTestScoreInfo(); + score.BeatmapInfo = beatmapInfo; + score.BeatmapHash = beatmapInfo.Hash; + score.Ruleset = beatmapInfo.Ruleset; + score.Rank = ScoreRank.D; + score.User = new APIUser { Username = "notme", Id = 5678 }; + scoreManager.Import(score); + }); + + AddStep("import convert score", () => + { + var score = TestResources.CreateTestScoreInfo(); + score.BeatmapInfo = beatmapInfo; + score.BeatmapHash = beatmapInfo.Hash; + score.Ruleset = new OsuRuleset().RulesetInfo; + score.User = API.LocalUser.Value; + scoreManager.Import(score); + }); + + AddStep("import correct score", () => + { + var score = TestResources.CreateTestScoreInfo(); + score.BeatmapInfo = beatmapInfo; + score.BeatmapHash = beatmapInfo.Hash; + score.Ruleset = beatmapInfo.Ruleset; + score.User = API.LocalUser.Value; + scoreManager.Import(score); + }); + + setUpTaggingRequests(() => beatmapInfo); + AddStep("load panel", () => + { + var score = TestResources.CreateTestScoreInfo(); + score.BeatmapInfo = beatmapInfo; + + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Child = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + Score = { Value = score }, + } + }; + }); + } + private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { Child = new StatisticsPanel diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 9ead9ce91c..ad868e58f0 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -14,14 +14,18 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.Placeholders; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics.User; using osuTK; +using Realms; namespace osu.Game.Screens.Ranking.Statistics { @@ -43,6 +47,9 @@ namespace osu.Game.Screens.Ranking.Statistics [Resolved] private BeatmapManager beatmapManager { get; set; } = null!; + [Resolved] + private RealmAccess realm { get; set; } = null!; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -231,17 +238,29 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - if (AchievedScore != null - && newScore.BeatmapInfo!.OnlineID > 0 + if (newScore.BeatmapInfo!.OnlineID > 0 && api.IsLoggedIn) { string? preventTaggingReason = null; // We may want to iterate on the following conditions further in the future - if (AchievedScore.Ruleset.OnlineID != AchievedScore.BeatmapInfo!.Ruleset.OnlineID) + var localUserScore = AchievedScore ?? realm.Run(r => + r.All() + .Filter($@"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $@" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $@" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}" + + $@" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, newScore.BeatmapInfo.ID, newScore.BeatmapInfo.Ruleset.ShortName) + .AsEnumerable() + .OrderByDescending(score => score.Ruleset.MatchesOnlineID(newScore.BeatmapInfo.Ruleset)) + .ThenByDescending(score => score.Rank) + .FirstOrDefault()); + + if (localUserScore == null) + preventTaggingReason = "Play the beatmap to contribute to beatmap tags!"; + else if (localUserScore.Ruleset.OnlineID != newScore.BeatmapInfo!.Ruleset.OnlineID) preventTaggingReason = "Play the beatmap in its original ruleset to contribute to beatmap tags!"; - else if (AchievedScore.Rank < ScoreRank.C) + else if (localUserScore.Rank < ScoreRank.C) preventTaggingReason = "Set a better score to contribute to beatmap tags!"; if (preventTaggingReason == null) From 90bc2318e15e9b2fbbc6bb75bb0d3857dfce7da7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Mar 2025 19:10:14 +0900 Subject: [PATCH 2/2] Remove unused local --- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 1749ea38b5..814c0519a3 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -298,7 +298,6 @@ namespace osu.Game.Tests.Visual.Ranking public void TestTaggingInteractionWithLocalScores() { BeatmapInfo beatmapInfo = null!; - string originalHash = string.Empty; AddStep(@"Import beatmap", () => {