From 27da3dc75a408b44ddf4dc1a38f4f0a9e55b8a57 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sat, 19 Jun 2021 20:54:24 +0800 Subject: [PATCH 01/73] added supporter-only-filter content --- .../BeatmapListingFilterControl.cs | 10 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 114 +++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 1935a250b7..3436a1b3b2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -24,6 +24,7 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. Contains only new items in the case of pagination. + /// Null when non-supporter user used supporter-only filters /// public Action> SearchFinished; @@ -212,7 +213,14 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; - SearchFinished?.Invoke(sets); + if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) + { + SearchFinished?.Invoke(null); + } + else + { + SearchFinished?.Invoke(sets); + } }; api.Queue(getSetsRequest); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5e65cd9488..63b9d3d34a 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -15,7 +15,9 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; @@ -33,6 +35,7 @@ namespace osu.Game.Overlays private Container panelTarget; private FillFlowContainer foundContent; private NotFoundDrawable notFoundContent; + private SupporterRequiredDrawable supporterRequiredContent; private BeatmapListingFilterControl filterControl; public BeatmapListingOverlay() @@ -76,6 +79,7 @@ namespace osu.Game.Overlays { foundContent = new FillFlowContainer(), notFoundContent = new NotFoundDrawable(), + supporterRequiredContent = new SupporterRequiredDrawable(), } } }, @@ -117,6 +121,13 @@ namespace osu.Game.Overlays private void onSearchFinished(List beatmaps) { + // non-supporter user used supporter-only filters + if (beatmaps == null) + { + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + return; + } + var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, @@ -170,7 +181,7 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent) + if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { // not found display may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); @@ -240,6 +251,107 @@ namespace osu.Game.Overlays } } + public class SupporterRequiredDrawable : CompositeDrawable + { + public SupporterRequiredDrawable() + { + RelativeSizeAxes = Axes.X; + Height = 250; + Alpha = 0; + Margin = new MarginPadding { Top = 15 }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + AddInternal(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Texture = textures.Get(@"Online/supporter-required"), + }, + createSupportRequiredText(), + } + }); + } + + private Drawable createSupportRequiredText() + { + LinkFlowContainer linkFlowContainer; + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault( + BeatmapsStrings.ListingSearchFiltersRank.ToString(), + "{1}" + ).ToString().Split("{1}"); + + // var titleContainer = new Container + // { + // RelativeSizeAxes = Axes.X, + // Margin = new MarginPadding { Vertical = 5 }, + // Children = new Drawable[] + // { + // linkFlowContainer = new LinkFlowContainer + // { + // Anchor = Anchor.Centre, + // Origin = Anchor.Centre, + // } + // } + // }; + + linkFlowContainer = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Bottom = 10, + } + }; + + linkFlowContainer.AddText( + text[0], + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ); + + linkFlowContainer.AddLink( + BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), + "https://osu.ppy.sh/store/products/supporter-tag", + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.AliceBlue; + } + ); + + linkFlowContainer.AddText( + text[1], + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ); + + return linkFlowContainer; + } + } + private const double time_between_fetches = 500; private double lastFetchDisplayedTime; From 42fdfbb9a1d2504da309c96ca23cdee4829819b2 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 17:17:07 +0800 Subject: [PATCH 02/73] added visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 74 +++++++++++++++++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 32 +++----- 2 files changed, 83 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 156d6b744e..86008ce173 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -14,6 +14,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { @@ -58,6 +59,79 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } + [Test] + public void TestSupporterOnlyFiltersPlaceholder() { + + AddStep("toggle non-supporter", () => + { + // non-supporter user + ((DummyAPIAccess)API).LocalUser.Value = new User + { + Username = API.LocalUser.Value.Username, + Id = API.LocalUser.Value.Id + 1, + IsSupporter = false, + }; + }); + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + + AddStep("toggle Random Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); + overlay.ChildrenOfType().Single().Ranks.Add(r); + // overlay.ChildrenOfType().Single().Ranks. + }); + + AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("Clear Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle Random Played filter", () => { + SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); + overlay.ChildrenOfType().Single().Played.Value = r; + }); + + AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("Clear Played filter", () => { + overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle supporter", () => + { + // supporter user + ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true; + }); + + AddStep("toggle Random Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); + overlay.ChildrenOfType().Single().Ranks.Add(r); + }); + + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => { + overlay.ChildrenOfType().Single().Ranks.Clear(); + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("toggle Random Played filter", () => { + SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); + overlay.ChildrenOfType().Single().Played.Value = r; + }); + + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Played filter", () => { + overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; + }); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 63b9d3d34a..3dec6de03a 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -181,11 +181,16 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent || lastContent == supporterRequiredContent) + if (lastContent == notFoundContent) { // not found display may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); } + else if (lastContent == supporterRequiredContent) + { + // supporter required display may be used multiple times, so don't expire/dispose it. + transform.Schedule(() => panelTarget.Remove(supporterRequiredContent)); + } else { // Consider the case when the new content is smaller than the last content. @@ -256,9 +261,8 @@ namespace osu.Game.Overlays public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; - Height = 250; + Height = 225; Alpha = 0; - Margin = new MarginPadding { Top = 15 }; } [BackgroundDependencyLoader] @@ -271,7 +275,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), Children = new Drawable[] { new Sprite @@ -290,24 +293,7 @@ namespace osu.Game.Overlays private Drawable createSupportRequiredText() { LinkFlowContainer linkFlowContainer; - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault( - BeatmapsStrings.ListingSearchFiltersRank.ToString(), - "{1}" - ).ToString().Split("{1}"); - - // var titleContainer = new Container - // { - // RelativeSizeAxes = Axes.X, - // Margin = new MarginPadding { Vertical = 5 }, - // Children = new Drawable[] - // { - // linkFlowContainer = new LinkFlowContainer - // { - // Anchor = Anchor.Centre, - // Origin = Anchor.Centre, - // } - // } - // }; + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(BeatmapsStrings.ListingSearchFiltersRank.ToString(), "{1}").ToString().Split("{1}"); linkFlowContainer = new LinkFlowContainer { @@ -335,7 +321,7 @@ namespace osu.Game.Overlays t => { t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.AliceBlue; + t.Colour = Colour4.FromHex("#A6C8D9"); } ); From e7aeba8d039cd8b3dfcaad98ed440d1cb4e9f620 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 18:28:43 +0800 Subject: [PATCH 03/73] added more visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 153 +++++++++++------- .../BeatmapListingFilterControl.cs | 1 + 2 files changed, 99 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 86008ce173..eebaea545a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -40,6 +40,16 @@ namespace osu.Game.Tests.Visual.Online return true; }; + + AddStep("initialize dummy", () => + { + // non-supporter user + ((DummyAPIAccess)API).LocalUser.Value = new User + { + Username = "TestBot", + Id = API.LocalUser.Value.Id + 1, + }; + }); } [Test] @@ -60,78 +70,95 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterOnlyFiltersPlaceholder() { + public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() + { + AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - AddStep("toggle non-supporter", () => - { - // non-supporter user - ((DummyAPIAccess)API).LocalUser.Value = new User - { - Username = API.LocalUser.Value.Username, - Id = API.LocalUser.Value.Id + 1, - IsSupporter = false, - }; - }); - AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + // test non-supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddStep("toggle Random Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); - overlay.ChildrenOfType().Single().Ranks.Add(r); - // overlay.ChildrenOfType().Single().Ranks. - }); - - AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - - AddStep("Clear Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - }); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle Random Played filter", () => { - SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); - overlay.ChildrenOfType().Single().Played.Value = r; - }); + // test non-supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("supporter-placeholder show", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - - AddStep("Clear Played filter", () => { - overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; - }); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle supporter", () => - { - // supporter user - ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true; - }); - - AddStep("toggle Random Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - Scoring.ScoreRank r = (Scoring.ScoreRank)(TestContext.CurrentContext.Random.NextShort() % 8); - overlay.ChildrenOfType().Single().Ranks.Add(r); - }); + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + // test supporter on Rank Achieved filter + toggleRandomRankFilter(); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("Clear Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - }); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("toggle Random Played filter", () => { - SearchPlayed r = (SearchPlayed)(TestContext.CurrentContext.Random.NextShort() % 2 + 1); - overlay.ChildrenOfType().Single().Played.Value = r; - }); - + // test supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddStep("Clear Played filter", () => { - overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any; - }); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } + [Test] + public void TestSupporterOnlyFiltersPlaceholderOneBeatmap() + { + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); + + // test non-supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + // test non-supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + + // test supporter on Rank Achieved filter + toggleRandomRankFilter(); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + // test supporter on Played filter + toggleRandomSupporterOnlyPlayedFilter(); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + + private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); @@ -141,6 +168,22 @@ namespace osu.Game.Tests.Visual.Online overlay.ChildrenOfType().Single().Query.TriggerChange(); } + private void toggleRandomRankFilter() + { + short r = TestContext.CurrentContext.Random.NextShort(); + AddStep("toggle Random Rank Achieved filter", () => + { + overlay.ChildrenOfType().Single().Ranks.Clear(); + overlay.ChildrenOfType().Single().Ranks.Add((Scoring.ScoreRank)(r % 8)); + }); + } + + private void toggleRandomSupporterOnlyPlayedFilter() + { + short r = TestContext.CurrentContext.Random.NextShort(); + AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); + } + private class TestAPIBeatmapSet : APIBeatmapSet { private readonly BeatmapSetInfo beatmapSet; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 3436a1b3b2..7b7f742b73 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -213,6 +213,7 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; + // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { SearchFinished?.Invoke(null); From 996503eb2d350aeae8252e9504541c82300da234 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 21:23:54 +0800 Subject: [PATCH 04/73] fixed filter text display, added visual tests --- .../Online/TestSceneBeatmapListingOverlay.cs | 106 ++++++++++++------ .../BeatmapListingFilterControl.cs | 8 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 45 ++++---- 3 files changed, 103 insertions(+), 56 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index eebaea545a..9128f72d6e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -72,45 +72,56 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() { + AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); // test non-supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); + + // test non-supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(true, false); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, true); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); // test supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + expectedPlaceholderShown(false, true); + + // test supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(false, true); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, true); } [Test] @@ -121,41 +132,51 @@ namespace osu.Game.Tests.Visual.Online // test non-supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); // test non-supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(true, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); + + // test non-supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(true, false); + + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + expectedPlaceholderShown(false, false); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter toggleRandomRankFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); // test supporter on Played filter toggleRandomSupporterOnlyPlayedFilter(); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + expectedPlaceholderShown(false, false); + + // test supporter on both Rank Achieved and Played filter + toggleRandomRankFilter(); + toggleRandomSupporterOnlyPlayedFilter(); + expectedPlaceholderShown(false, false); + + AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); + AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); + expectedPlaceholderShown(false, false); } @@ -184,6 +205,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); } + private void expectedPlaceholderShown(bool supporterRequiredShown, bool notFoundShown) + { + if (supporterRequiredShown) + { + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + else + { + AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + + if (notFoundShown) + { + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + else + { + AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); + } + } + private class TestAPIBeatmapSet : APIBeatmapSet { private readonly BeatmapSetInfo beatmapSet; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 7b7f742b73..6e83dc0bf4 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. Contains only new items in the case of pagination. - /// Null when non-supporter user used supporter-only filters + /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. /// - public Action> SearchFinished; + public Action, BeatmapListingSearchControl> SearchFinished; /// /// Fired when search criteria change. @@ -216,11 +216,11 @@ namespace osu.Game.Overlays.BeatmapListing // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { - SearchFinished?.Invoke(null); + SearchFinished?.Invoke(sets, searchControl); } else { - SearchFinished?.Invoke(sets); + SearchFinished?.Invoke(sets, null); } }; diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 3dec6de03a..90f1e6932d 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -119,11 +119,17 @@ namespace osu.Game.Overlays private Task panelLoadDelegate; - private void onSearchFinished(List beatmaps) + private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) { // non-supporter user used supporter-only filters - if (beatmaps == null) + if (searchControl != null) { + // compose filter string + List filtersStrs = new List(); + if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); + if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); return; } @@ -258,11 +264,24 @@ namespace osu.Game.Overlays public class SupporterRequiredDrawable : CompositeDrawable { + private LinkFlowContainer linkFlowContainer; + public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; Height = 225; Alpha = 0; + + linkFlowContainer = linkFlowContainer = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Bottom = 10, + } + }; } [BackgroundDependencyLoader] @@ -285,27 +304,15 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - createSupportRequiredText(), + linkFlowContainer, } }); } - private Drawable createSupportRequiredText() - { - LinkFlowContainer linkFlowContainer; - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(BeatmapsStrings.ListingSearchFiltersRank.ToString(), "{1}").ToString().Split("{1}"); - - linkFlowContainer = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding - { - Bottom = 10, - } - }; + public void UpdateSupportRequiredText(string filtersStr) { + string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(filtersStr, "{1}").ToString().Split("{1}"); + linkFlowContainer.Clear(); linkFlowContainer.AddText( text[0], t => @@ -333,8 +340,6 @@ namespace osu.Game.Overlays t.Colour = Colour4.White; } ); - - return linkFlowContainer; } } From d0a8b748238dfa03b9174db624c34bac0259ad2a Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Sun, 20 Jun 2021 21:28:57 +0800 Subject: [PATCH 05/73] fixed filter text order --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 90f1e6932d..ceb033380c 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -126,8 +126,8 @@ namespace osu.Game.Overlays { // compose filter string List filtersStrs = new List(); - if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); From 0707642b76d94b81f3ef061bc6f207f7edf85764 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:25:20 +0800 Subject: [PATCH 06/73] fixed SupporterRequiredDrawable --- osu.Game/Overlays/BeatmapListingOverlay.cs | 89 +++++++++------------- 1 file changed, 34 insertions(+), 55 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index ceb033380c..5ddad1c9fc 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -121,25 +122,20 @@ namespace osu.Game.Overlays private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) { - // non-supporter user used supporter-only filters - if (searchControl != null) - { - // compose filter string - List filtersStrs = new List(); - if (searchControl.Played.Value != SearchPlayed.Any) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); - if (searchControl.Ranks.Any()) filtersStrs.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); - supporterRequiredContent.UpdateSupportRequiredText(string.Join(" and ", filtersStrs)); - - LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); - return; - } - var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }); + // non-supporter user used supporter-only filters + if (searchControl != null) + { + supporterRequiredContent.UpdateText(searchControl.Played.Value != SearchPlayed.Any, searchControl.Ranks.Any()); + LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + return; + } + if (filterControl.CurrentPage == 0) { //No matches case @@ -262,26 +258,16 @@ namespace osu.Game.Overlays } } + // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private LinkFlowContainer linkFlowContainer; + private OsuSpriteText supporterRequiredText; public SupporterRequiredDrawable() { RelativeSizeAxes = Axes.X; Height = 225; Alpha = 0; - - linkFlowContainer = linkFlowContainer = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding - { - Bottom = 10, - } - }; } [BackgroundDependencyLoader] @@ -304,42 +290,35 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - linkFlowContainer, + supporterRequiredText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Bottom = 10 }, + }, + createSupporterTagLink(), } }); } - public void UpdateSupportRequiredText(string filtersStr) { - string[] text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(filtersStr, "{1}").ToString().Split("{1}"); + public void UpdateText(bool playedFilter, bool rankFilter) { + List filters = new List(); + if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); + if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); + supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); + } - linkFlowContainer.Clear(); - linkFlowContainer.AddText( - text[0], - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.White; - } - ); + public Drawable createSupporterTagLink() { + LinkFlowContainer supporterTagLink = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 10 }, + }; - linkFlowContainer.AddLink( - BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), - "https://osu.ppy.sh/store/products/supporter-tag", - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.FromHex("#A6C8D9"); - } - ); - - linkFlowContainer.AddText( - text[1], - t => - { - t.Font = OsuFont.GetFont(size: 16); - t.Colour = Colour4.White; - } - ); + supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), Online.Chat.LinkAction.External, "https://osu.ppy.sh/store/products/supporter-tag"); + return supporterTagLink; } } From 33674563041102d02722fd98366eab6d3c48aa92 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:30:54 +0800 Subject: [PATCH 07/73] fixed SupporterRequiredDrawable style --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5ddad1c9fc..42a7253276 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -294,6 +294,8 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 16), + Colour = Colour4.White, Margin = new MarginPadding { Bottom = 10 }, }, createSupporterTagLink(), From b42aedeb8171352bb56ed5334b0a347f43865335 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 14:43:54 +0800 Subject: [PATCH 08/73] fixed code style --- osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 9128f72d6e..2146ea333a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -179,7 +179,6 @@ namespace osu.Game.Tests.Visual.Online expectedPlaceholderShown(false, false); } - private void fetchFor(params BeatmapSetInfo[] beatmaps) { setsForResponse.Clear(); From 22cc1e14527c14bb2aa16052cdd5bb3ce27072dc Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 15:31:47 +0800 Subject: [PATCH 09/73] fixed code style base on code analysis --- osu.Game/Overlays/BeatmapListingOverlay.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 42a7253276..407b737db5 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -280,7 +279,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Children = new Drawable[] + Children = new[] { new Sprite { @@ -303,14 +302,16 @@ namespace osu.Game.Overlays }); } - public void UpdateText(bool playedFilter, bool rankFilter) { + public void UpdateText(bool playedFilter, bool rankFilter) + { List filters = new List(); if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); } - public Drawable createSupporterTagLink() { + private Drawable createSupporterTagLink() + { LinkFlowContainer supporterTagLink = new LinkFlowContainer { Anchor = Anchor.Centre, From b162da5ee0265726f99b8c847b7b31512cae0e88 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Mon, 21 Jun 2021 15:47:47 +0800 Subject: [PATCH 10/73] minor code change --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 407b737db5..578e70e630 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -320,7 +320,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Bottom = 10 }, }; - supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), Online.Chat.LinkAction.External, "https://osu.ppy.sh/store/products/supporter-tag"); + supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), "https://osu.ppy.sh/store/products/supporter-tag"); return supporterTagLink; } } From 0d17fb425973e8935220d06a2f40ff3223b23b86 Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Tue, 22 Jun 2021 13:53:21 +0800 Subject: [PATCH 11/73] fixed code --- .../Online/TestSceneBeatmapListingOverlay.cs | 194 +++++++++--------- .../BeatmapListingFilterControl.cs | 41 +++- osu.Game/Overlays/BeatmapListingOverlay.cs | 70 +++---- 3 files changed, 170 insertions(+), 135 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 2146ea333a..cd382c2bb2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual.Online private BeatmapListingOverlay overlay; + private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType().Single(); + [BackgroundDependencyLoader] private void load() { @@ -70,113 +72,123 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestNonSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, true); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + notFoundPlaceholderShown(); // test non-supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); // test non-supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); - - AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - - // test supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, true); - - // test supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); - - // test supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, true); - - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, true); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); } [Test] - public void TestSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + { + AddStep("fetch for 0 beatmaps", () => fetchFor()); + AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); + + // test supporter on Rank Achieved filter + toggleRankFilter(Scoring.ScoreRank.XH); + notFoundPlaceholderShown(); + + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + notFoundPlaceholderShown(); + + // test supporter on Played filter + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + notFoundPlaceholderShown(); + + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); + + // test supporter on both Rank Achieved and Played filter + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + notFoundPlaceholderShown(); + + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + notFoundPlaceholderShown(); + } + + [Test] + public void TestNonSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); // test non-supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); // test non-supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); // test non-supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(true, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); + } + [Test] + public void TestSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + { + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); // test supporter on Rank Achieved filter - toggleRandomRankFilter(); - expectedPlaceholderShown(false, false); + toggleRankFilter(Scoring.ScoreRank.XH); + noPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); // test supporter on Played filter - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, false); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + noPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + noPlaceholderShown(); // test supporter on both Rank Achieved and Played filter - toggleRandomRankFilter(); - toggleRandomSupporterOnlyPlayedFilter(); - expectedPlaceholderShown(false, false); + toggleRankFilter(Scoring.ScoreRank.XH); + toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + noPlaceholderShown(); - AddStep("Set Played filter to Any", () => overlay.ChildrenOfType().Single().Played.Value = SearchPlayed.Any); - AddStep("Clear Rank Achieved filter", () => overlay.ChildrenOfType().Single().Ranks.Clear()); - expectedPlaceholderShown(false, false); + AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + noPlaceholderShown(); } private void fetchFor(params BeatmapSetInfo[] beatmaps) @@ -185,44 +197,36 @@ namespace osu.Game.Tests.Visual.Online setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); // trigger arbitrary change for fetching. - overlay.ChildrenOfType().Single().Query.TriggerChange(); + searchControl.Query.TriggerChange(); } - private void toggleRandomRankFilter() + private void toggleRankFilter(Scoring.ScoreRank rank) { - short r = TestContext.CurrentContext.Random.NextShort(); - AddStep("toggle Random Rank Achieved filter", () => + AddStep("toggle Rank Achieved filter", () => { - overlay.ChildrenOfType().Single().Ranks.Clear(); - overlay.ChildrenOfType().Single().Ranks.Add((Scoring.ScoreRank)(r % 8)); + searchControl.Ranks.Clear(); + searchControl.Ranks.Add(rank); }); } - private void toggleRandomSupporterOnlyPlayedFilter() + private void toggleSupporterOnlyPlayedFilter(SearchPlayed played) { - short r = TestContext.CurrentContext.Random.NextShort(); - AddStep("toggle Random Played filter", () => overlay.ChildrenOfType().Single().Played.Value = (SearchPlayed)(r % 2 + 1)); + AddStep("toggle Played filter", () => searchControl.Played.Value = played); } - private void expectedPlaceholderShown(bool supporterRequiredShown, bool notFoundShown) + private void supporterRequiredPlaceholderShown() { - if (supporterRequiredShown) - { - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - } - else - { - AddUntilStep("supporter-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - } + AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } - if (notFoundShown) - { - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - } - else - { - AddUntilStep("not-found-placeholder hidden", () => !overlay.ChildrenOfType().Any()); - } + private void notFoundPlaceholderShown() + { + AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + + private void noPlaceholderShown() + { + AddUntilStep("no placeholder shown", () => !overlay.ChildrenOfType().Any() && !overlay.ChildrenOfType().Any()); } private class TestAPIBeatmapSet : APIBeatmapSet diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 6e83dc0bf4..f49d913bb2 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -10,11 +10,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -26,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapListing /// Fired when a search finishes. Contains only new items in the case of pagination. /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. /// - public Action, BeatmapListingSearchControl> SearchFinished; + public Action SearchFinished; /// /// Fired when search criteria change. @@ -216,11 +218,19 @@ namespace osu.Game.Overlays.BeatmapListing // check if an non-supporter user used supporter-only filters if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) { - SearchFinished?.Invoke(sets, searchControl); + List filters = new List(); + + if (searchControl.Played.Value != SearchPlayed.Any) + filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed); + + if (searchControl.Ranks.Any()) + filters.Add(BeatmapsStrings.ListingSearchFiltersRank); + + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); } else { - SearchFinished?.Invoke(sets, null); + SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); } }; @@ -246,5 +256,30 @@ namespace osu.Game.Overlays.BeatmapListing base.Dispose(isDisposing); } + + public enum SearchResultType + { + ResultsReturned, + SupporterOnlyFilter + } + + public struct SearchResult + { + public SearchResultType Type { get; private set; } + public List Results { get; private set; } + public List Filters { get; private set; } + + public static SearchResult ResultsReturned(List results) => new SearchResult + { + Type = SearchResultType.ResultsReturned, + Results = results + }; + + public static SearchResult SupporterOnlyFilter(List filters) => new SearchResult + { + Type = SearchResultType.SupporterOnlyFilter, + Filters = filters + }; + } } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 578e70e630..63800e6585 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Localisation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -119,28 +120,28 @@ namespace osu.Game.Overlays private Task panelLoadDelegate; - private void onSearchFinished(List beatmaps, BeatmapListingSearchControl searchControl) + private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { - var newPanels = beatmaps.Select(b => new GridBeatmapPanel(b) + // non-supporter user used supporter-only filters + if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilter) + { + supporterRequiredContent.UpdateText(searchResult.Filters); + addContentToPlaceholder(supporterRequiredContent); + return; + } + + var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }); - // non-supporter user used supporter-only filters - if (searchControl != null) - { - supporterRequiredContent.UpdateText(searchControl.Played.Value != SearchPlayed.Any, searchControl.Ranks.Any()); - LoadComponentAsync(supporterRequiredContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); - return; - } - if (filterControl.CurrentPage == 0) { //No matches case if (!newPanels.Any()) { - LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token); + addContentToPlaceholder(notFoundContent); return; } @@ -182,16 +183,11 @@ namespace osu.Game.Overlays { var transform = lastContent.FadeOut(100, Easing.OutQuint); - if (lastContent == notFoundContent) + if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { - // not found display may be used multiple times, so don't expire/dispose it. + // the placeholder may be used multiple times, so don't expire/dispose it. transform.Schedule(() => panelTarget.Remove(lastContent)); } - else if (lastContent == supporterRequiredContent) - { - // supporter required display may be used multiple times, so don't expire/dispose it. - transform.Schedule(() => panelTarget.Remove(supporterRequiredContent)); - } else { // Consider the case when the new content is smaller than the last content. @@ -260,7 +256,7 @@ namespace osu.Game.Overlays // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private OsuSpriteText supporterRequiredText; + private OsuSpriteText filtersText; public SupporterRequiredDrawable() { @@ -289,30 +285,20 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - supporterRequiredText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 16), - Colour = Colour4.White, - Margin = new MarginPadding { Bottom = 10 }, - }, - createSupporterTagLink(), + createSupporterText(), } }); } - public void UpdateText(bool playedFilter, bool rankFilter) + public void UpdateText(List filters) { - List filters = new List(); - if (playedFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersPlayed.ToString()); - if (rankFilter) filters.Add(BeatmapsStrings.ListingSearchFiltersRank.ToString()); - supporterRequiredText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); + // use string literals for now + filtersText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); } - private Drawable createSupporterTagLink() + private Drawable createSupporterText() { - LinkFlowContainer supporterTagLink = new LinkFlowContainer + LinkFlowContainer supporterRequiredText = new LinkFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -320,8 +306,18 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Bottom = 10 }, }; - supporterTagLink.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), "https://osu.ppy.sh/store/products/supporter-tag"); - return supporterTagLink; + filtersText = (OsuSpriteText)supporterRequiredText.AddText( + "_", + t => + { + t.Font = OsuFont.GetFont(size: 16); + t.Colour = Colour4.White; + } + ).First(); + + supporterRequiredText.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), @"/store/products/supporter-tag"); + + return supporterRequiredText; } } From 9cb9ef5c563316df57f7a49400359463ead3f49b Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Tue, 22 Jun 2021 13:31:41 -0700 Subject: [PATCH 12/73] Refactor the menu's max height to be a property --- osu.Game/Overlays/Settings/SettingsEnumDropdown.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index 9987a0c607..fa9973fb08 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -14,13 +14,15 @@ namespace osu.Game.Overlays.Settings protected new class DropdownControl : OsuEnumDropdown { + protected virtual int MenuMaxHeight => 200; + public DropdownControl() { Margin = new MarginPadding { Top = 5 }; RelativeSizeAxes = Axes.X; } - protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = MenuMaxHeight); } } } From 59928a7d91d230e32fec56d395d65c485457cc52 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Tue, 22 Jun 2021 13:32:45 -0700 Subject: [PATCH 13/73] Decrease the max height of dropdown menus in mod settings --- osu.Game/Configuration/SettingSourceAttribute.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 3e50613093..cd0652f401 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -12,6 +12,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Configuration @@ -149,7 +150,7 @@ namespace osu.Game.Configuration break; case IBindable bindable: - var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); + var dropdownType = typeof(ModSettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); var dropdown = (Drawable)Activator.CreateInstance(dropdownType); dropdownType.GetProperty(nameof(SettingsDropdown.LabelText))?.SetValue(dropdown, attr.Label); @@ -183,5 +184,16 @@ namespace osu.Game.Configuration => obj.GetSettingsSourceProperties() .OrderBy(attr => attr.Item1) .ToArray(); + + private class ModSettingsEnumDropdown : SettingsEnumDropdown + where T : struct, Enum + { + protected override OsuDropdown CreateDropdown() => new ModDropdownControl(); + + private class ModDropdownControl : DropdownControl + { + protected override int MenuMaxHeight => 100; + } + } } } From bae42a89089559b7beecec677664fd57aa8705e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Jun 2021 14:07:37 +0900 Subject: [PATCH 14/73] Add inline comment explaining why the height is set lower --- osu.Game/Configuration/SettingSourceAttribute.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index cd0652f401..cee5883d9a 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -192,6 +192,7 @@ namespace osu.Game.Configuration private class ModDropdownControl : DropdownControl { + // Set low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). protected override int MenuMaxHeight => 100; } } From c9a203cc11f12532054493629617e21daaba4b21 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 15:03:34 +0900 Subject: [PATCH 15/73] Move collections db in-place --- osu.Game/Collections/CollectionManager.cs | 41 ++++++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index b53cc659f7..b2f36c75f4 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -257,27 +257,42 @@ namespace osu.Game.Collections { Interlocked.Increment(ref lastSave); + // This is NOT thread-safe!! try { - // This is NOT thread-safe!! + var tempPath = Path.GetTempFileName(); - using (var sw = new SerializationWriter(storage.GetStream(database_name, FileAccess.Write))) + using (var ms = new MemoryStream()) { - sw.Write(database_version); - - var collectionsCopy = Collections.ToArray(); - sw.Write(collectionsCopy.Length); - - foreach (var c in collectionsCopy) + using (var sw = new SerializationWriter(ms, true)) { - sw.Write(c.Name.Value); + sw.Write(database_version); - var beatmapsCopy = c.Beatmaps.ToArray(); - sw.Write(beatmapsCopy.Length); + var collectionsCopy = Collections.ToArray(); + sw.Write(collectionsCopy.Length); - foreach (var b in beatmapsCopy) - sw.Write(b.MD5Hash); + foreach (var c in collectionsCopy) + { + sw.Write(c.Name.Value); + + var beatmapsCopy = c.Beatmaps.ToArray(); + sw.Write(beatmapsCopy.Length); + + foreach (var b in beatmapsCopy) + sw.Write(b.MD5Hash); + } } + + using (var fs = File.OpenWrite(tempPath)) + ms.WriteTo(fs); + + var storagePath = storage.GetFullPath(database_name); + var storageBackupPath = storage.GetFullPath($"{database_name}.bak"); + if (File.Exists(storageBackupPath)) + File.Delete(storageBackupPath); + if (File.Exists(storagePath)) + File.Move(storagePath, storageBackupPath); + File.Move(tempPath, storagePath); } if (saveFailures < 10) From 5828606c1332152b71c942ae2b06d7d98dc445ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 15:09:42 +0900 Subject: [PATCH 16/73] Prefer using the backup file if it exists --- osu.Game/Collections/CollectionManager.cs | 30 ++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index b2f36c75f4..04ea7eaebf 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -35,6 +35,7 @@ namespace osu.Game.Collections private const int database_version = 30000000; private const string database_name = "collection.db"; + private const string database_backup_name = "collection.db.bak"; public readonly BindableList Collections = new BindableList(); @@ -56,11 +57,14 @@ namespace osu.Game.Collections { Collections.CollectionChanged += collectionsChanged; - if (storage.Exists(database_name)) + // If a backup file exists, it means the previous write operation didn't complete successfully. Always prefer the backup file in such a case. + string filename = storage.Exists(database_backup_name) ? database_backup_name : database_name; + + if (storage.Exists(filename)) { List beatmapCollections; - using (var stream = storage.GetStream(database_name)) + using (var stream = storage.GetStream(filename)) beatmapCollections = readCollections(stream); // intentionally fire-and-forget async. @@ -286,13 +290,21 @@ namespace osu.Game.Collections using (var fs = File.OpenWrite(tempPath)) ms.WriteTo(fs); - var storagePath = storage.GetFullPath(database_name); - var storageBackupPath = storage.GetFullPath($"{database_name}.bak"); - if (File.Exists(storageBackupPath)) - File.Delete(storageBackupPath); - if (File.Exists(storagePath)) - File.Move(storagePath, storageBackupPath); - File.Move(tempPath, storagePath); + var databasePath = storage.GetFullPath(database_name); + var databaseBackupPath = storage.GetFullPath(database_backup_name); + + // Back up the existing database, clearing any existing backup. + if (File.Exists(databaseBackupPath)) + File.Delete(databaseBackupPath); + if (File.Exists(databasePath)) + File.Move(databasePath, databaseBackupPath); + + // Move the new database in-place of the existing one. + File.Move(tempPath, databasePath); + + // If everything succeeded up to this point, remove the backup file. + if (File.Exists(databaseBackupPath)) + File.Delete(databaseBackupPath); } if (saveFailures < 10) From 0510282dcbe1a3ad581d01ed57d1bfcfea431c18 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 15:10:03 +0900 Subject: [PATCH 17/73] Add missing ctor --- osu.Game/IO/Legacy/SerializationWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index bb8014fe54..9ebeaf616e 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -18,8 +18,8 @@ namespace osu.Game.IO.Legacy /// handle null strings and simplify use with ISerializable. public class SerializationWriter : BinaryWriter { - public SerializationWriter(Stream s) - : base(s, Encoding.UTF8) + public SerializationWriter(Stream s, bool leaveOpen = false) + : base(s, Encoding.UTF8, leaveOpen) { } From e7981eae9bbca5e40afbe9103628cfe17f9af9ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 16:14:05 +0900 Subject: [PATCH 18/73] Safeguard load procedure a bit more --- osu.Game/Collections/CollectionManager.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 04ea7eaebf..e6e8bedcf4 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -55,16 +55,26 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load() { + if (storage.Exists(database_backup_name)) + { + // If a backup file exists, it means the previous write operation didn't run to completion. + // Always prefer the backup file in such a case as it's the most recent copy that is guaranteed to not be malformed. + // + // The database is saved 100ms after any change, and again when the game is closed, so there shouldn't be a large diff between the two files in the worst case. + if (storage.Exists(database_name)) + storage.Delete(database_name); + File.Copy(storage.GetFullPath(database_backup_name), storage.GetFullPath(database_name)); + } + + // Only accept changes after/if the above succeeds to prevent simultaneously accessing either file. + // Todo: This really should not be delayed like this, but is unlikely to cause issues because CollectionManager loads very early in execution. Collections.CollectionChanged += collectionsChanged; - // If a backup file exists, it means the previous write operation didn't complete successfully. Always prefer the backup file in such a case. - string filename = storage.Exists(database_backup_name) ? database_backup_name : database_name; - - if (storage.Exists(filename)) + if (storage.Exists(database_name)) { List beatmapCollections; - using (var stream = storage.GetStream(filename)) + using (var stream = storage.GetStream(database_name)) beatmapCollections = readCollections(stream); // intentionally fire-and-forget async. From 8a2026e3053c9c8bbc7d46c25029fa3d7f7f0506 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 23 Jun 2021 21:26:52 +0900 Subject: [PATCH 19/73] Schedule callback instead --- osu.Game/Collections/CollectionManager.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index e6e8bedcf4..fe04c70d62 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -55,6 +55,8 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load() { + Collections.CollectionChanged += collectionsChanged; + if (storage.Exists(database_backup_name)) { // If a backup file exists, it means the previous write operation didn't run to completion. @@ -66,10 +68,6 @@ namespace osu.Game.Collections File.Copy(storage.GetFullPath(database_backup_name), storage.GetFullPath(database_name)); } - // Only accept changes after/if the above succeeds to prevent simultaneously accessing either file. - // Todo: This really should not be delayed like this, but is unlikely to cause issues because CollectionManager loads very early in execution. - Collections.CollectionChanged += collectionsChanged; - if (storage.Exists(database_name)) { List beatmapCollections; @@ -82,7 +80,7 @@ namespace osu.Game.Collections } } - private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { switch (e.Action) { @@ -106,7 +104,7 @@ namespace osu.Game.Collections } backgroundSave(); - } + }); /// /// Set an endpoint for notifications to be posted to. From 263370e1ff946475f394c74c3c9e6aebab2d064d Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 23 Jun 2021 16:42:14 +0200 Subject: [PATCH 20/73] Localize ruleset filter any filter button. --- .../BeatmapSearchRulesetFilterRow.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs index c2d0eea80c..e2c84c537c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; @@ -22,14 +23,21 @@ namespace osu.Game.Overlays.BeatmapListing [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - AddItem(new RulesetInfo - { - Name = @"Any" - }); + AddTabItem(new RulesetFilterTabItemAny()); foreach (var r in rulesets.AvailableRulesets) AddItem(r); } } + + private class RulesetFilterTabItemAny : FilterTabItem + { + protected override LocalisableString LabelFor(RulesetInfo info) => BeatmapsStrings.ModeAny; + + public RulesetFilterTabItemAny() + : base(new RulesetInfo()) + { + } + } } } From d2d0f3bf9ba7e46922c03f95c33d1ca18eb33d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Jun 2021 17:55:40 +0200 Subject: [PATCH 21/73] Set more appropriate build time limits for GH Actions workflows --- .github/workflows/ci.yml | 1 + .github/workflows/report-nunit.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed3e99cb61..29cbdd2d37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: - { prettyname: macOS, fullname: macos-latest } - { prettyname: Linux, fullname: ubuntu-latest } threadingMode: ['SingleThread', 'MultiThreaded'] + timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml index 381d2d49c5..e0ccd50989 100644 --- a/.github/workflows/report-nunit.yml +++ b/.github/workflows/report-nunit.yml @@ -21,6 +21,7 @@ jobs: - { prettyname: macOS } - { prettyname: Linux } threadingMode: ['SingleThread', 'MultiThreaded'] + timeout-minutes: 5 steps: - name: Annotate CI run with test results uses: dorny/test-reporter@v1.4.2 From c8022126dce165d7cd0e159e74c5f9cc726ef0b2 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Wed, 23 Jun 2021 13:39:12 -0700 Subject: [PATCH 22/73] Add logo sound case for transitioning to song select --- osu.Game/Screens/Menu/ButtonSystem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index a836f7bf09..da0edd07db 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -274,6 +274,10 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Play: buttonsPlay.First().Click(); return false; + + // no sound should be played if the logo is clicked on while transitioning to song select + case ButtonSystemState.EnteringMode: + return false; } } From 73590bfca1a4131581aa74c6d7b28a8378729221 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 24 Jun 2021 07:20:31 +0800 Subject: [PATCH 23/73] Return an empty array when the sender is from system. --- osu.Game/Overlays/Chat/ChatLine.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index f43420e35e..8666d20159 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -240,6 +240,9 @@ namespace osu.Game.Overlays.Chat { get { + if (sender.Id == User.SYSTEM_USER.Id) + return Array.Empty(); + List items = new List { new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action) From 564c72bf74d6e5053f627d539f0118a9c4ee84e8 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 24 Jun 2021 10:10:57 +0800 Subject: [PATCH 24/73] compare directly instead of comparing IDs --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 8666d20159..987dc27802 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -240,7 +240,7 @@ namespace osu.Game.Overlays.Chat { get { - if (sender.Id == User.SYSTEM_USER.Id) + if (sender == User.SYSTEM_USER) return Array.Empty(); List items = new List From 812624a502cdff0cd276d53857f83516fc9d07da Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Thu, 24 Jun 2021 10:45:20 +0800 Subject: [PATCH 25/73] use `.Equals()` instead --- 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 987dc27802..01cfe9a55b 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -240,7 +240,7 @@ namespace osu.Game.Overlays.Chat { get { - if (sender == User.SYSTEM_USER) + if (sender.Equals(User.SYSTEM_USER)) return Array.Empty(); List items = new List @@ -248,7 +248,7 @@ namespace osu.Game.Overlays.Chat new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action) }; - if (sender.Id != api.LocalUser.Value.Id) + if (!sender.Equals(api.LocalUser.Value)) items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction)); return items.ToArray(); From 2268d7f8a586f3121c420e2f04ceaf9bcd2a05b1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 13:19:42 +0800 Subject: [PATCH 26/73] Extract utility methods into helper class; Better xmldoc and naming --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 79 +--------------- osu.Game.Rulesets.Osu/Utils/VectorHelper.cs | 100 ++++++++++++++++++++ 2 files changed, 102 insertions(+), 77 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Utils/VectorHelper.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 97e3d82664..bcd5274e02 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -23,15 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => "It never gets boring!"; - // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. - // The closer the hit objects draw to the border, the sharper the turn - private const float playfield_edge_ratio = 0.375f; - - private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio; - private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio; - - private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2; - private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; private Random rng; @@ -113,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = getRotatedVector(previous.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = VectorHelper.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); @@ -185,73 +177,6 @@ namespace osu.Game.Rulesets.Osu.Mods } } - /// - /// Determines the position of the current hit object relative to the previous one. - /// - /// The position of the current hit object relative to the previous one - private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) - { - var relativeRotationDistance = 0f; - - if (prevPosChanged.X < playfield_middle.X) - { - relativeRotationDistance = Math.Max( - (border_distance_x - prevPosChanged.X) / border_distance_x, - relativeRotationDistance - ); - } - else - { - relativeRotationDistance = Math.Max( - (prevPosChanged.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, - relativeRotationDistance - ); - } - - if (prevPosChanged.Y < playfield_middle.Y) - { - relativeRotationDistance = Math.Max( - (border_distance_y - prevPosChanged.Y) / border_distance_y, - relativeRotationDistance - ); - } - else - { - relativeRotationDistance = Math.Max( - (prevPosChanged.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, - relativeRotationDistance - ); - } - - return rotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevPosChanged, relativeRotationDistance / 2); - } - - /// - /// Rotates vector "initial" towards vector "destinantion" - /// - /// Vector to rotate to "destination" - /// Vector "initial" should be rotated to - /// The angle the vector should be rotated relative to the difference between the angles of the the two vectors. - /// Resulting vector - private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance) - { - var initialAngleRad = Math.Atan2(initial.Y, initial.X); - var destAngleRad = Math.Atan2(destination.Y, destination.X); - - var diff = destAngleRad - initialAngleRad; - - while (diff < -Math.PI) diff += 2 * Math.PI; - - while (diff > Math.PI) diff -= 2 * Math.PI; - - var finalAngleRad = initialAngleRad + relativeDistance * diff; - - return new Vector2( - initial.Length * (float)Math.Cos(finalAngleRad), - initial.Length * (float)Math.Sin(finalAngleRad) - ); - } - private class RandomObjectInfo { public float AngleRad { get; set; } diff --git a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs new file mode 100644 index 0000000000..bc482a3dd7 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs @@ -0,0 +1,100 @@ +// 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.Game.Rulesets.Osu.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Utils +{ + public static class VectorHelper + { + // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. + // The closer the hit objects draw to the border, the sharper the turn + private const float playfield_edge_ratio = 0.375f; + + private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio; + private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio; + + private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2; + + /// + /// Rotate a hit object away from the playfield edge, while keeping a constant distance + /// from the previous object. + /// + /// + /// The extent of rotation depends on the position of the hit object. Hit objects + /// closer to the playfield edge will be rotated to a larger extent. + /// + /// Position of the previous hit object. + /// Position of the hit object to be rotated, relative to the previous hit object. + /// + /// The extent of rotation. + /// 0 means the hit object is never rotated. + /// 1 means the hit object will be fully rotated towards playfield center when it is originally at playfield edge. + /// + /// The new position of the hit object, relative to the previous one. + public static Vector2 RotateAwayFromEdge(Vector2 prevObjectPos, Vector2 posRelativeToPrev, float rotationRatio = 0.5f) + { + var relativeRotationDistance = 0f; + + if (prevObjectPos.X < playfield_middle.X) + { + relativeRotationDistance = Math.Max( + (border_distance_x - prevObjectPos.X) / border_distance_x, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevObjectPos.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, + relativeRotationDistance + ); + } + + if (prevObjectPos.Y < playfield_middle.Y) + { + relativeRotationDistance = Math.Max( + (border_distance_y - prevObjectPos.Y) / border_distance_y, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevObjectPos.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, + relativeRotationDistance + ); + } + + return RotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevObjectPos, relativeRotationDistance * rotationRatio); + } + + /// + /// Rotates vector "initial" towards vector "destination". + /// + /// The vector to be rotated. + /// The vector that "initial" should be rotated towards. + /// How much "initial" should be rotated. 0 means no rotation. 1 means "initial" is fully rotated to equal "destination". + /// The rotated vector. + public static Vector2 RotateVectorTowardsVector(Vector2 initial, Vector2 destination, float rotationRatio) + { + var initialAngleRad = Math.Atan2(initial.Y, initial.X); + var destAngleRad = Math.Atan2(destination.Y, destination.X); + + var diff = destAngleRad - initialAngleRad; + + while (diff < -Math.PI) diff += 2 * Math.PI; + + while (diff > Math.PI) diff -= 2 * Math.PI; + + var finalAngleRad = initialAngleRad + rotationRatio * diff; + + return new Vector2( + initial.Length * (float)Math.Cos(finalAngleRad), + initial.Length * (float)Math.Sin(finalAngleRad) + ); + } + } +} From 153e204d200bf554b19236c5fc283982047e148c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 13:22:10 +0800 Subject: [PATCH 27/73] Cap rotation ratio to 1 --- osu.Game.Rulesets.Osu/Utils/VectorHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs index bc482a3dd7..e3fe3249e8 100644 --- a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs +++ b/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs @@ -68,7 +68,11 @@ namespace osu.Game.Rulesets.Osu.Utils ); } - return RotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevObjectPos, relativeRotationDistance * rotationRatio); + return RotateVectorTowardsVector( + posRelativeToPrev, + playfield_middle - prevObjectPos, + Math.Min(1, relativeRotationDistance * rotationRatio) + ); } /// From 27735eeedba9d3947ff403d1db12924f789b13ed Mon Sep 17 00:00:00 2001 From: JimmyC7834 Date: Thu, 24 Jun 2021 13:45:38 +0800 Subject: [PATCH 28/73] fixed code --- .../BeatmapListingFilterControl.cs | 23 ++++++++----- osu.Game/Overlays/BeatmapListingOverlay.cs | 34 +++++++------------ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index f49d913bb2..b6a0846407 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -25,8 +25,9 @@ namespace osu.Game.Overlays.BeatmapListing public class BeatmapListingFilterControl : CompositeDrawable { /// - /// Fired when a search finishes. Contains only new items in the case of pagination. - /// Fired with BeatmapListingSearchControl when non-supporter user used supporter-only filters. + /// Fired when a search finishes. + /// SearchFinished.Type = ResultsReturned when results returned. Contains only new items in the case of pagination. + /// SearchFinished.Type = SupporterOnlyFilter when a non-supporter user applied supporter-only filters. /// public Action SearchFinished; @@ -216,7 +217,7 @@ namespace osu.Game.Overlays.BeatmapListing getSetsRequest = null; // check if an non-supporter user used supporter-only filters - if (!api.LocalUser.Value.IsSupporter && (searchControl.Ranks.Any() || searchControl.Played.Value != SearchPlayed.Any)) + if (!api.LocalUser.Value.IsSupporter) { List filters = new List(); @@ -226,12 +227,14 @@ namespace osu.Game.Overlays.BeatmapListing if (searchControl.Ranks.Any()) filters.Add(BeatmapsStrings.ListingSearchFiltersRank); - SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); - } - else - { - SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); + if (filters.Any()) + { + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); + return; + } } + + SearchFinished?.Invoke(SearchResult.ResultsReturned(sets)); }; api.Queue(getSetsRequest); @@ -259,10 +262,14 @@ namespace osu.Game.Overlays.BeatmapListing public enum SearchResultType { + // returned with Results ResultsReturned, + // non-supporter user applied supporter-only filters SupporterOnlyFilter } + // Results only valid when Type == ResultsReturned + // Filters only valid when Type == SupporterOnlyFilter public struct SearchResult { public SearchResultType Type { get; private set; } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 63800e6585..c2ba3d5bc0 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -256,7 +256,7 @@ namespace osu.Game.Overlays // using string literals as there's no proper processing for LocalizeStrings yet public class SupporterRequiredDrawable : CompositeDrawable { - private OsuSpriteText filtersText; + private LinkFlowContainer supporterRequiredText; public SupporterRequiredDrawable() { @@ -275,7 +275,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Children = new[] + Children = new Drawable[] { new Sprite { @@ -285,39 +285,31 @@ namespace osu.Game.Overlays FillMode = FillMode.Fit, Texture = textures.Get(@"Online/supporter-required"), }, - createSupporterText(), + supporterRequiredText = new LinkFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 10 }, + }, } }); } public void UpdateText(List filters) { - // use string literals for now - filtersText.Text = BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(); - } + supporterRequiredText.Clear(); - private Drawable createSupporterText() - { - LinkFlowContainer supporterRequiredText = new LinkFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Bottom = 10 }, - }; - - filtersText = (OsuSpriteText)supporterRequiredText.AddText( - "_", + supporterRequiredText.AddText( + BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(), t => { t.Font = OsuFont.GetFont(size: 16); t.Colour = Colour4.White; } - ).First(); + ); supporterRequiredText.AddLink(BeatmapsStrings.ListingSearchSupporterFilterQuoteLinkText.ToString(), @"/store/products/supporter-tag"); - - return supporterRequiredText; } } From 16d58935352391052c4ffce4fcf98b875ef0dbca Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 24 Jun 2021 15:02:45 +0900 Subject: [PATCH 29/73] Add `DroppedObjectContainer` class --- .../TestSceneCatcher.cs | 6 +++--- .../TestSceneCatcherArea.cs | 8 ++------ .../TestSceneHyperDashColouring.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 6 +----- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 ++-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- .../UI/DroppedObjectContainer.cs | 17 +++++++++++++++++ 7 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 900691ecae..28b1aaf03a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private OsuConfigManager config { get; set; } - private Container droppedObjectContainer; + private DroppedObjectContainer droppedObjectContainer; private TestCatcher catcher; @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Tests }; var trailContainer = new Container(); - droppedObjectContainer = new Container(); + droppedObjectContainer = new DroppedObjectContainer(); catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty); Child = new Container @@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Catch.Tests { public IEnumerable CaughtObjects => this.ChildrenOfType(); - public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty) + public TestCatcher(Container trailsTarget, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty) : base(trailsTarget, droppedObjectTarget, difficulty) { } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 4af5098451..dca75eee14 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Framework.Utils; @@ -97,10 +96,7 @@ namespace osu.Game.Rulesets.Catch.Tests SetContents(_ => { - var droppedObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both - }; + var droppedObjectContainer = new DroppedObjectContainer(); return new CatchInputManager(catchRuleset) { @@ -126,7 +122,7 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestCatcherArea : CatcherArea { - public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) + public TestCatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) : base(droppedObjectContainer, beatmapDifficulty) { } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 683a776dcc..4af528ef22 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("create hyper-dashing catcher", () => { - Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container()) + Child = setupSkinHierarchy(catcherArea = new CatcherArea(new DroppedObjectContainer()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 644facdabc..21501398fc 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; @@ -35,10 +34,7 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfield(BeatmapDifficulty difficulty) { - var droppedObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }; + var droppedObjectContainer = new DroppedObjectContainer(); CatcherArea = new CatcherArea(droppedObjectContainer, difficulty) { diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 1f01dbabb5..6b42e3ddd3 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Contains objects dropped from the plate. /// - private readonly Container droppedObjectTarget; + private readonly DroppedObjectContainer droppedObjectTarget; public CatcherAnimationState CurrentState { @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly DrawablePool caughtBananaPool; private readonly DrawablePool caughtDropletPool; - public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null) + public Catcher([NotNull] Container trailsTarget, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null) { this.trailsTarget = trailsTarget; this.droppedObjectTarget = droppedObjectTarget; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index cdb15c2b4c..951a10fc01 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.UI /// private int currentDirection; - public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null) + public CatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); Children = new Drawable[] diff --git a/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs b/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs new file mode 100644 index 0000000000..b44b0caae4 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs @@ -0,0 +1,17 @@ +// 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; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Catch.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class DroppedObjectContainer : Container + { + public DroppedObjectContainer() + { + RelativeSizeAxes = Axes.Both; + } + } +} From ae09c23e4e262d3f6cc815ead57aec7f734a1f60 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 24 Jun 2021 15:56:53 +0900 Subject: [PATCH 30/73] Resolve `DroppedObjectContainer` via DI --- .../TestSceneCatcher.cs | 38 +++++++++++-------- .../TestSceneCatcherArea.cs | 13 ++++--- .../TestSceneHyperDashColouring.cs | 18 +++++++-- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 9 +++-- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 +-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 +- 6 files changed, 54 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 28b1aaf03a..1ad45d2f13 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Game.Rulesets.Catch.UI; using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.UI; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; @@ -31,10 +31,23 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private OsuConfigManager config { get; set; } - private DroppedObjectContainer droppedObjectContainer; + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + private readonly Container trailContainer; private TestCatcher catcher; + public TestSceneCatcher() + { + Add(trailContainer = new Container + { + Anchor = Anchor.Centre, + Depth = -1 + }); + Add(droppedObjectContainer = new DroppedObjectContainer()); + } + [SetUp] public void SetUp() => Schedule(() => { @@ -43,20 +56,13 @@ namespace osu.Game.Rulesets.Catch.Tests CircleSize = 0, }; - var trailContainer = new Container(); - droppedObjectContainer = new DroppedObjectContainer(); - catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty); + if (catcher != null) + Remove(catcher); - Child = new Container + Add(catcher = new TestCatcher(trailContainer, difficulty) { - Anchor = Anchor.Centre, - Children = new Drawable[] - { - trailContainer, - droppedObjectContainer, - catcher - } - }; + Anchor = Anchor.Centre + }); }); [Test] @@ -293,8 +299,8 @@ namespace osu.Game.Rulesets.Catch.Tests { public IEnumerable CaughtObjects => this.ChildrenOfType(); - public TestCatcher(Container trailsTarget, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty) - : base(trailsTarget, droppedObjectTarget, difficulty) + public TestCatcher(Container trailsTarget, BeatmapDifficulty difficulty) + : base(trailsTarget, difficulty) { } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index dca75eee14..877e115e2f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -96,15 +96,12 @@ namespace osu.Game.Rulesets.Catch.Tests SetContents(_ => { - var droppedObjectContainer = new DroppedObjectContainer(); - return new CatchInputManager(catchRuleset) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - droppedObjectContainer, - new TestCatcherArea(droppedObjectContainer, beatmapDifficulty) + new TestCatcherArea(beatmapDifficulty) { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, @@ -122,9 +119,13 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestCatcherArea : CatcherArea { - public TestCatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) - : base(droppedObjectContainer, beatmapDifficulty) + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) + : base(beatmapDifficulty) { + AddInternal(droppedObjectContainer = new DroppedObjectContainer()); } public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 4af528ef22..7fa981d492 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -118,11 +118,10 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("create hyper-dashing catcher", () => { - Child = setupSkinHierarchy(catcherArea = new CatcherArea(new DroppedObjectContainer()) + Child = setupSkinHierarchy(catcherArea = new TestCatcherArea { Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), + Origin = Anchor.Centre }, skin); }); @@ -206,5 +205,18 @@ namespace osu.Game.Rulesets.Catch.Tests { } } + + private class TestCatcherArea : CatcherArea + { + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + public TestCatcherArea() + { + Scale = new Vector2(4f); + + AddInternal(droppedObjectContainer = new DroppedObjectContainer()); + } + } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 21501398fc..05cd29dff5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Catch.UI /// public const float CENTER_X = WIDTH / 2; + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + internal readonly CatcherArea CatcherArea; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => @@ -34,9 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfield(BeatmapDifficulty difficulty) { - var droppedObjectContainer = new DroppedObjectContainer(); - - CatcherArea = new CatcherArea(droppedObjectContainer, difficulty) + CatcherArea = new CatcherArea(difficulty) { Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, @@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new[] { - droppedObjectContainer, + droppedObjectContainer = new DroppedObjectContainer(), CatcherArea.MovableCatcher.CreateProxiedContent(), HitObjectContainer.CreateProxy(), // This ordering (`CatcherArea` before `HitObjectContainer`) is important to diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 6b42e3ddd3..dcab9459ee 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -79,7 +79,8 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Contains objects dropped from the plate. /// - private readonly DroppedObjectContainer droppedObjectTarget; + [Resolved] + private DroppedObjectContainer droppedObjectTarget { get; set; } public CatcherAnimationState CurrentState { @@ -134,10 +135,9 @@ namespace osu.Game.Rulesets.Catch.UI private readonly DrawablePool caughtBananaPool; private readonly DrawablePool caughtDropletPool; - public Catcher([NotNull] Container trailsTarget, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null) + public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null) { this.trailsTarget = trailsTarget; - this.droppedObjectTarget = droppedObjectTarget; Origin = Anchor.TopCentre; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 951a10fc01..fea314df8d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.UI /// private int currentDirection; - public CatcherArea(DroppedObjectContainer droppedObjectContainer, BeatmapDifficulty difficulty = null) + public CatcherArea(BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); Children = new Drawable[] @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.UI Margin = new MarginPadding { Bottom = 350f }, X = CatchPlayfield.CENTER_X }, - MovableCatcher = new Catcher(this, droppedObjectContainer, difficulty) { X = CatchPlayfield.CENTER_X }, + MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X }, }; } From c0c1b8d62014bb0c23dad2e3b924051af5467303 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 24 Jun 2021 16:12:43 +0900 Subject: [PATCH 31/73] Fix catcher hyper-dash afterimage is not always displayed --- osu.Game.Rulesets.Catch/UI/CatcherTrail.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs index 80522ab36b..c961d98dc5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs @@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override void FreeAfterUse() { ClearTransforms(); + Alpha = 1; base.FreeAfterUse(); } } From 3fcda83713111f24fc1436adfdd588d2aa6cea3a Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 24 Jun 2021 22:00:19 +0800 Subject: [PATCH 32/73] Rename `VectorHelper` to `VectorUtils` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- osu.Game.Rulesets.Osu/Utils/{VectorHelper.cs => VectorUtils.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/Utils/{VectorHelper.cs => VectorUtils.cs} (99%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index bcd5274e02..7c7e88cbba 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = VectorHelper.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = VectorUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); diff --git a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs b/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs similarity index 99% rename from osu.Game.Rulesets.Osu/Utils/VectorHelper.cs rename to osu.Game.Rulesets.Osu/Utils/VectorUtils.cs index e3fe3249e8..a829c89636 100644 --- a/osu.Game.Rulesets.Osu/Utils/VectorHelper.cs +++ b/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - public static class VectorHelper + public static class VectorUtils { // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn From 4fe30ca9235d1fa1937879b2896cdddb7a3c21c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 24 Jun 2021 18:31:13 +0200 Subject: [PATCH 33/73] Remove Traditional Chinese (HK) language option Due to no actual existing translations it was falling back to Simplified Chinese instead, making the option actively wrong/confusing. --- osu.Game/Localisation/Language.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index 3c66f31c58..dc1fac47a8 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -103,8 +103,11 @@ namespace osu.Game.Localisation [Description(@"简体中文")] zh, - [Description(@"繁體中文(香港)")] - zh_hk, + // Traditional Chinese (Hong Kong) is listed in web sources but has no associated localisations, + // and was wrongly falling back to Simplified Chinese. + // Can be revisited if localisations ever arrive. + // [Description(@"繁體中文(香港)")] + // zh_hk, [Description(@"繁體中文(台灣)")] zh_tw From 62566f2a4a0caf84a211e6cf7469f04abfa5900f Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:29:47 -0700 Subject: [PATCH 34/73] Remove "Score Multiplier" text --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e31e307d4d..85dcade986 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -330,22 +330,12 @@ namespace osu.Game.Overlays.Mods Spacing = new Vector2(footer_button_spacing / 2, 0), Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Children = new Drawable[] + Child = MultiplierLabel = new OsuSpriteText { - new OsuSpriteText - { - Text = @"Score Multiplier:", - Font = OsuFont.GetFont(size: 30), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - MultiplierLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. - }, + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. }, }, } From 34ace2553e5b8db36be5dd0447bac569eeb2d843 Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:32:00 -0700 Subject: [PATCH 35/73] Revert "Refactor the menu's max height to be a property" This reverts commit 9cb9ef5c --- osu.Game/Overlays/Settings/SettingsEnumDropdown.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index fa9973fb08..9987a0c607 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -14,15 +14,13 @@ namespace osu.Game.Overlays.Settings protected new class DropdownControl : OsuEnumDropdown { - protected virtual int MenuMaxHeight => 200; - public DropdownControl() { Margin = new MarginPadding { Top = 5 }; RelativeSizeAxes = Axes.X; } - protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = MenuMaxHeight); + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); } } } From 93edb25acefe5004a7fd4e032d8a7696ca02bbdd Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:32:43 -0700 Subject: [PATCH 36/73] Override `CreateMenu` instead of using a property --- osu.Game/Configuration/SettingSourceAttribute.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index cee5883d9a..55636495df 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -192,8 +192,8 @@ namespace osu.Game.Configuration private class ModDropdownControl : DropdownControl { - // Set low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). - protected override int MenuMaxHeight => 100; + // Set menu's max height low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 100); } } } From 26086ca1efc5107ab3c125a24a146db17940e1bb Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 25 Jun 2021 09:43:14 +0800 Subject: [PATCH 37/73] Rename `VectorUtils` to `OsuHitObjectGenerationUtils` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- .../Utils/{VectorUtils.cs => OsuHitObjectGenerationUtils.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Osu/Utils/{VectorUtils.cs => OsuHitObjectGenerationUtils.cs} (98%) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 7c7e88cbba..d1212096bf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = VectorUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); diff --git a/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs similarity index 98% rename from osu.Game.Rulesets.Osu/Utils/VectorUtils.cs rename to osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index a829c89636..2a60124757 100644 --- a/osu.Game.Rulesets.Osu/Utils/VectorUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - public static class VectorUtils + public static class OsuHitObjectGenerationUtils { // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn From ec8810cc2b96e92108c5fd365366c0fe8e9d97be Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 25 Jun 2021 09:44:23 +0800 Subject: [PATCH 38/73] Use `MathF` instead of `(float)Math` --- .../Utils/OsuHitObjectGenerationUtils.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 2a60124757..06b964a647 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -84,20 +84,20 @@ namespace osu.Game.Rulesets.Osu.Utils /// The rotated vector. public static Vector2 RotateVectorTowardsVector(Vector2 initial, Vector2 destination, float rotationRatio) { - var initialAngleRad = Math.Atan2(initial.Y, initial.X); - var destAngleRad = Math.Atan2(destination.Y, destination.X); + var initialAngleRad = MathF.Atan2(initial.Y, initial.X); + var destAngleRad = MathF.Atan2(destination.Y, destination.X); var diff = destAngleRad - initialAngleRad; - while (diff < -Math.PI) diff += 2 * Math.PI; + while (diff < -MathF.PI) diff += 2 * MathF.PI; - while (diff > Math.PI) diff -= 2 * Math.PI; + while (diff > MathF.PI) diff -= 2 * MathF.PI; var finalAngleRad = initialAngleRad + rotationRatio * diff; return new Vector2( - initial.Length * (float)Math.Cos(finalAngleRad), - initial.Length * (float)Math.Sin(finalAngleRad) + initial.Length * MathF.Cos(finalAngleRad), + initial.Length * MathF.Sin(finalAngleRad) ); } } From 58839221771cf68b5d95fc5101befb3bdf70250f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Jun 2021 16:36:31 +0900 Subject: [PATCH 39/73] Remove mod multiplier completely --- .../TestSceneModSelectOverlay.cs | 22 +--------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 42 +------------------ .../OnlinePlay/FreeModSelectOverlay.cs | 1 - 3 files changed, 3 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 2885dbee00..df8ef92a05 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; @@ -107,7 +106,6 @@ namespace osu.Game.Tests.Visual.UserInterface var conversionMods = osu.GetModsFor(ModType.Conversion); var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); - var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); @@ -120,8 +118,6 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiMod(doubleTimeMod); testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour); - testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour); testUnimplementedMod(targetMod); } @@ -149,7 +145,7 @@ namespace osu.Game.Tests.Visual.UserInterface changeRuleset(0); - AddAssert("ensure mods still selected", () => modDisplay.Current.Value.Single(m => m is OsuModNoFail) != null); + AddAssert("ensure mods still selected", () => modDisplay.Current.Value.SingleOrDefault(m => m is OsuModNoFail) != null); changeRuleset(3); @@ -316,17 +312,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); } - private void testMultiplierTextColour(Mod mod, Func getCorrectColour) - { - checkLabelColor(() => Color4.White); - selectNext(mod); - AddWaitStep("wait for changing colour", 1); - checkLabelColor(getCorrectColour); - selectPrevious(mod); - AddWaitStep("wait for changing colour", 1); - checkLabelColor(() => Color4.White); - } - private void testModsWithSameBaseType(Mod modA, Mod modB) { selectNext(modA); @@ -348,7 +333,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert($"check {mod.Name} is selected", () => { var button = modSelect.GetModButton(mod); - return modSelect.SelectedMods.Value.Single(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; + return modSelect.SelectedMods.Value.SingleOrDefault(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; }); } @@ -370,8 +355,6 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour()); - private void createDisplay(Func createOverlayFunc) { Children = new Drawable[] @@ -408,7 +391,6 @@ namespace osu.Game.Tests.Visual.UserInterface return section.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); } - public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; public new TriangleButton DeselectAllButton => base.DeselectAllButton; public new Color4 LowMultiplierColour => base.LowMultiplierColour; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 85dcade986..e4aab978fc 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -37,9 +37,6 @@ namespace osu.Game.Overlays.Mods protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; - protected readonly Drawable MultiplierSection; - protected readonly OsuSpriteText MultiplierLabel; - protected readonly FillFlowContainer FooterContainer; protected override bool BlockNonPositionalInput => false; @@ -324,20 +321,6 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - MultiplierSection = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(footer_button_spacing / 2, 0), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Child = MultiplierLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. - }, - }, } } }, @@ -351,11 +334,8 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, AudioManager audio, OsuGameBase osu) + private void load(AudioManager audio, OsuGameBase osu) { - LowMultiplierColour = colours.Red; - HighMultiplierColour = colours.Green; - availableMods = osu.AvailableMods.GetBoundCopy(); sampleOn = audio.Samples.Get(@"UI/check-on"); @@ -485,26 +465,6 @@ namespace osu.Game.Overlays.Mods foreach (var section in ModSectionsContainer.Children) section.UpdateSelectedButtons(selectedMods); - - updateMultiplier(); - } - - private void updateMultiplier() - { - var multiplier = 1.0; - - foreach (var mod in SelectedMods.Value) - { - multiplier *= mod.ScoreMultiplier; - } - - MultiplierLabel.Text = $"{multiplier:N2}x"; - if (multiplier > 1.0) - MultiplierLabel.FadeColour(HighMultiplierColour, 200); - else if (multiplier < 1.0) - MultiplierLabel.FadeColour(LowMultiplierColour, 200); - else - MultiplierLabel.FadeColour(Color4.White, 200); } private void modButtonPressed(Mod selectedMod) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 5e2e9fd087..d5abaaab4e 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -32,7 +32,6 @@ namespace osu.Game.Screens.OnlinePlay { IsValidMod = m => true; - MultiplierSection.Alpha = 0; DeselectAllButton.Alpha = 0; Drawable selectAllButton; From 6bc71590c539ef0dba6993aab0a5a48a95dfea7e Mon Sep 17 00:00:00 2001 From: aitani9 <55509723+aitani9@users.noreply.github.com> Date: Fri, 25 Jun 2021 09:21:26 -0700 Subject: [PATCH 40/73] Disable logo click sound when exiting --- osu.Game/Screens/Menu/ButtonSystem.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index da0edd07db..38290a6530 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Menu switch (state) { default: - return true; + return false; case ButtonSystemState.Initial: State = ButtonSystemState.TopLevel; @@ -274,10 +274,6 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Play: buttonsPlay.First().Click(); return false; - - // no sound should be played if the logo is clicked on while transitioning to song select - case ButtonSystemState.EnteringMode: - return false; } } From 50c27d26357f2646548c126c7201398a23ee489e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Jun 2021 19:10:04 +0200 Subject: [PATCH 41/73] Update usages of `IHasTooltip` in line with framework localisation changes --- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 3 ++- .../Sliders/Components/PathControlPointPiece.cs | 3 ++- osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs | 3 ++- osu.Game/Configuration/SettingSourceAttribute.cs | 2 +- osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs | 3 ++- osu.Game/Graphics/Containers/OsuClickableContainer.cs | 3 ++- osu.Game/Graphics/Cursor/OsuTooltipContainer.cs | 3 ++- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 3 ++- osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs | 3 ++- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 3 ++- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 5 +++-- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 2 +- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 3 ++- .../Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs | 3 ++- osu.Game/Overlays/Comments/DrawableComment.cs | 5 +++-- osu.Game/Overlays/Mods/ModButton.cs | 3 ++- osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs | 3 ++- .../Overlays/Profile/Header/Components/DrawableBadge.cs | 3 ++- .../Profile/Header/Components/ExpandDetailsButton.cs | 3 ++- .../Overlays/Profile/Header/Components/FollowersButton.cs | 3 ++- osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs | 3 ++- .../Profile/Header/Components/LevelProgressBar.cs | 3 ++- .../Profile/Header/Components/MappingSubscribersButton.cs | 3 ++- .../Profile/Header/Components/MessageUserButton.cs | 3 ++- .../Profile/Header/Components/OverlinedTotalPlayTime.cs | 3 ++- .../Overlays/Profile/Header/Components/SupporterIcon.cs | 3 ++- .../Sections/Historical/DrawableMostPlayedBeatmap.cs | 2 +- osu.Game/Overlays/RestoreDefaultValueButton.cs | 3 ++- .../Overlays/Settings/Sections/Audio/OffsetSettings.cs | 3 ++- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- .../Overlays/Settings/Sections/Input/MouseSettings.cs | 3 ++- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 3 ++- .../Settings/Sections/UserInterface/GeneralSettings.cs | 3 ++- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 5 +++-- osu.Game/Overlays/Settings/SettingsButton.cs | 8 +++++--- osu.Game/Overlays/Settings/SettingsItem.cs | 4 ++-- osu.Game/Rulesets/UI/ModIcon.cs | 3 ++- .../Screens/Edit/Compose/Components/SelectionBoxButton.cs | 3 ++- .../Screens/OnlinePlay/Components/DrawableGameType.cs | 3 ++- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 7 ++++--- osu.Game/Users/Drawables/DrawableFlag.cs | 3 ++- 42 files changed, 87 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 1c89d9cd00..f89750a96e 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mania.Configuration; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Mania private class TimeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString("N0") + "ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 48e4db11ca..5b476526c9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; @@ -283,6 +284,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - public string TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; + public LocalisableString TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs index c5374d50ab..096bccae9e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -59,7 +60,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class Icon : Container, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public SpriteIcon SpriteIcon { get; } diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 55636495df..f373e59417 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -31,7 +31,7 @@ namespace osu.Game.Configuration { public LocalisableString Label { get; } - public string Description { get; } + public LocalisableString Description { get; } public int? OrderPosition { get; } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs index 75c73af0ce..ce8a9c8f9f 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs @@ -4,12 +4,13 @@ using Markdig.Syntax.Inlines; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownImage : MarkdownImage, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public OsuMarkdownImage(LinkInline linkInline) : base(linkInline.Url) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 60ded8952d..0bc3c876e1 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.Containers @@ -24,7 +25,7 @@ namespace osu.Game.Graphics.Containers this.sampleSet = sampleSet; } - public virtual string TooltipText { get; set; } + public virtual LocalisableString TooltipText { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 57f39bb8c7..81dca99ddd 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.Cursor @@ -32,7 +33,7 @@ namespace osu.Game.Graphics.Cursor public override bool SetContent(object content) { - if (!(content is string contentString)) + if (!(content is LocalisableString contentString)) return false; if (contentString == text.Text) return true; diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 5a1eb53fe1..6ad88eaaba 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Platform; using osuTK; using osuTK.Graphics; @@ -58,6 +59,6 @@ namespace osu.Game.Graphics.UserInterface return true; } - public string TooltipText => "view in browser"; + public LocalisableString TooltipText => "view in browser"; } } diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index ac6f5ceb1b..8e82f4a7c1 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Platform; namespace osu.Game.Graphics.UserInterface @@ -105,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface private class CapsWarning : SpriteIcon, IHasTooltip { - public string TooltipText => @"caps lock is active"; + public LocalisableString TooltipText => "caps lock is active"; public CapsWarning() { diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index f58962f8e1..ae16169123 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -34,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual string TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 795540b65d..e35d3d6461 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -295,7 +296,7 @@ namespace osu.Game.Online.Leaderboards public override bool Contains(Vector2 screenSpacePos) => content.Contains(screenSpacePos); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public ScoreComponentLabel(LeaderboardScoreStatistic statistic) { @@ -365,7 +366,7 @@ namespace osu.Game.Online.Leaderboards }; } - public string TooltipText { get; } + public LocalisableString TooltipText { get; } } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index cf74c0d4d3..b81c60a5b9 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.BeatmapSet { private readonly OsuSpriteText value; - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public LocalisableString Value { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 7ad6906cea..bb87e7151b 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly IBindable localUser = new Bindable(); - public string TooltipText + public LocalisableString TooltipText { get { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index 6d27342049..cef623e59b 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -26,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly bool noVideo; - public string TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download"; + public LocalisableString TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download"; private readonly IBindable localUser = new Bindable(); diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 7c47ac655f..d94f8c4b8b 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -20,6 +20,7 @@ using System; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.IEnumerableExtensions; using System.Collections.Specialized; +using osu.Framework.Localisation; using osu.Game.Overlays.Comments.Buttons; namespace osu.Game.Overlays.Comments @@ -395,7 +396,7 @@ namespace osu.Game.Overlays.Comments private class ParentUsername : FillFlowContainer, IHasTooltip { - public string TooltipText => getParentMessage(); + public LocalisableString TooltipText => getParentMessage(); private readonly Comment parentComment; @@ -427,7 +428,7 @@ namespace osu.Game.Overlays.Comments if (parentComment == null) return string.Empty; - return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? @"deleted" : string.Empty; + return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? "deleted" : string.Empty; } } } diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 70424101fd..d0bd24496a 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -14,6 +14,7 @@ using System; using System.Linq; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -34,7 +35,7 @@ namespace osu.Game.Overlays.Mods /// public Action SelectionChanged; - public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; + public LocalisableString TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; private const Easing mod_switch_easing = Easing.InOutSine; private const double mod_switch_duration = 120; diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs index 87b9d89d4d..0ece96b56c 100644 --- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs +++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.UserInterface; using osu.Framework.Allocation; using osuTK.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; namespace osu.Game.Overlays { @@ -56,7 +57,7 @@ namespace osu.Game.Overlays [Resolved] private OverlayColourProvider colourProvider { get; set; } - public string TooltipText => $@"{Value} view"; + public LocalisableString TooltipText => $@"{Value} view"; private readonly SpriteIcon icon; diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs index 7eed4d3b6b..74f3ed846b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Users; using osuTK; @@ -42,6 +43,6 @@ namespace osu.Game.Overlays.Profile.Header.Components InternalChild.FadeInFromZero(200); } - public string TooltipText => badge.Description; + public LocalisableString TooltipText => badge.Description; } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index 29e13e4f51..527c70685f 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osuTK; namespace osu.Game.Overlays.Profile.Header.Components @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly BindableBool DetailsVisible = new BindableBool(); - public override string TooltipText => DetailsVisible.Value ? "collapse" : "expand"; + public override LocalisableString TooltipText => DetailsVisible.Value ? "collapse" : "expand"; private SpriteIcon icon; diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index bd8aa7b3bd..db94762efd 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -12,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "followers"; + public override LocalisableString TooltipText => "followers"; protected override IconUsage Icon => FontAwesome.Solid.User; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index 29471375b5..a0b8ef0f11 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } private OsuSpriteText levelText; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index c97df3bc4d..528b05a80a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; } + public LocalisableString TooltipText { get; } private Bar levelProgressBar; private OsuSpriteText levelProgressText; diff --git a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs index b4d7c9a05c..ae3d024fbf 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -12,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "mapping subscribers"; + public override LocalisableString TooltipText => "mapping subscribers"; protected override IconUsage Icon => FontAwesome.Solid.Bell; diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs index 228765ee1a..4c2cc768ce 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Users; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public override string TooltipText => "send message"; + public override LocalisableString TooltipText => "send message"; [Resolved(CanBeNull = true)] private ChannelManager channelManager { get; set; } diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs index be96840217..aa7cb8636a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } private OverlinedInfoContainer info; diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index d581e2750c..9a43997030 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; namespace osu.Game.Overlays.Profile.Header.Components @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private readonly FillFlowContainer iconContainer; private readonly CircularContainer content; - public string TooltipText => "osu!supporter"; + public LocalisableString TooltipText => "osu!supporter"; public int SupportLevel { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 6d6ff32aac..6f1869966a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private class PlayCountText : CompositeDrawable, IHasTooltip { - public string TooltipText => "times played"; + public LocalisableString TooltipText => "times played"; public PlayCountText(int playCount) { diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 213ad2ba68..fe36f6ba6d 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -76,7 +77,7 @@ namespace osu.Game.Overlays UpdateState(); } - public string TooltipText => "revert to default"; + public LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) { diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index c9a81b955b..1ae297f2a9 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -32,7 +33,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private class OffsetSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0ms"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0ms"); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 937bcc8abf..669753d2cb 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -233,7 +233,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private class UIScaleSlider : OsuSliderBar { - public override string TooltipText => base.TooltipText + "x"; + public override LocalisableString TooltipText => base.TooltipText + "x"; } private class ResolutionSettingsDropdown : SettingsDropdown diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index fb908a7669..e87572e2ca 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Input; @@ -116,7 +117,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySlider : OsuSliderBar { - public override string TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x"; + public override LocalisableString TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index 101d8f43f7..8aeb440be1 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections @@ -10,6 +11,6 @@ namespace osu.Game.Overlays.Settings.Sections /// internal class SizeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0.##x"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 19adfc5dd9..a6eb008623 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -44,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class TimeSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString("N0") + "ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index c73a783d37..2470c0a6c5 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -62,12 +63,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class MaximumStarsSlider : StarsSlider { - public override string TooltipText => Current.IsDefault ? "no limit" : base.TooltipText; + public override LocalisableString TooltipText => Current.IsDefault ? "no limit" : base.TooltipText; } private class StarsSlider : OsuSliderBar { - public override string TooltipText => Current.Value.ToString(@"0.## stars"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); } } } diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 088d69c031..87b1aa0e46 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings @@ -17,14 +18,15 @@ namespace osu.Game.Overlays.Settings Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; } - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } public override IEnumerable FilterTerms { get { - if (TooltipText != null) - return base.FilterTerms.Append(TooltipText); + if (TooltipText != default) + // TODO: this won't work as intended once the tooltip text is translated. + return base.FilterTerms.Append(TooltipText.ToString()); return base.FilterTerms; } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 807916e7f6..15a0a42d31 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings public bool ShowsDefaultIndicator = true; - public string TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } [Resolved] private OsuColour colours { get; set; } @@ -142,4 +142,4 @@ namespace osu.Game.Overlays.Settings labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; } } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index cae5da3d16..725cfa9c26 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osuTK; using osu.Framework.Bindables; +using osu.Framework.Localisation; namespace osu.Game.Rulesets.UI { @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - public virtual string TooltipText => showTooltip ? mod.IconTooltip : null; + public virtual LocalisableString TooltipText => showTooltip ? mod.IconTooltip : null; private Mod mod; private readonly bool showTooltip; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 3b1dae6c3d..3ac40fda0f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osuTK; using osuTK.Graphics; @@ -58,6 +59,6 @@ namespace osu.Game.Screens.Edit.Compose.Components icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } - public string TooltipText { get; } + public LocalisableString TooltipText { get; } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs b/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs index c4dc2a2b8f..ae1ca1b967 100644 --- a/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs +++ b/osu.Game/Screens/OnlinePlay/Components/DrawableGameType.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Online.Rooms; @@ -16,7 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { private readonly GameType type; - public string TooltipText => type.Name; + public LocalisableString TooltipText => type.Name; public DrawableGameType(GameType type) { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index e1cf0cef4e..4a35202df2 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -431,7 +431,7 @@ namespace osu.Game.Screens.Select public class InfoLabel : Container, IHasTooltip { - public string TooltipText { get; } + public LocalisableString TooltipText { get; } public InfoLabel(BeatmapStatistic statistic) { diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index c3bf740108..f73489ac61 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; namespace osu.Game.Users.Drawables @@ -68,11 +69,11 @@ namespace osu.Game.Users.Drawables private class ClickableArea : OsuClickableContainer { - private string tooltip = default_tooltip_text; + private LocalisableString tooltip = default_tooltip_text; - public override string TooltipText + public override LocalisableString TooltipText { - get => Enabled.Value ? tooltip : null; + get => Enabled.Value ? tooltip : default; set => tooltip = value; } diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 1d648e46b6..aea40a01ae 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; namespace osu.Game.Users.Drawables { @@ -13,7 +14,7 @@ namespace osu.Game.Users.Drawables { private readonly Country country; - public string TooltipText => country?.FullName; + public LocalisableString TooltipText => country?.FullName; public DrawableFlag(Country country) { From 8e1bcc4d6bd751a5112451f3f9c0ed06a8937714 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:02:31 +0700 Subject: [PATCH 42/73] add overall difficulty in filter criteria --- osu.Game/Screens/Select/FilterCriteria.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 208048380a..b9e912df8e 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.Select public OptionalRange ApproachRate; public OptionalRange DrainRate; public OptionalRange CircleSize; + public OptionalRange OverallDifficulty; public OptionalRange Length; public OptionalRange BPM; public OptionalRange BeatDivisor; From 4df4afe533aed92ee7c8d7f22d14b1047007283b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:02:57 +0700 Subject: [PATCH 43/73] add test for overall difficulty filter query --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9bd262a569..a55bdd2df8 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -90,6 +90,20 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.Less(filterCriteria.DrainRate.Min, 6.1f); } + [Test] + public void TestApplyOverallDifficultyQueries() + { + const string query = "od>4 easy od<8"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.Greater(filterCriteria.OverallDifficulty.Min, 4.0); + Assert.Less(filterCriteria.OverallDifficulty.Min, 4.1); + Assert.Greater(filterCriteria.OverallDifficulty.Max, 7.9); + Assert.Less(filterCriteria.OverallDifficulty.Max, 8.0); + } + [Test] public void TestApplyBPMQueries() { From 2b1d3c8e9c9ce4eb5e5790032d64da556a0f7c6f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sat, 26 Jun 2021 21:05:01 +0700 Subject: [PATCH 44/73] add od filter in search filter --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 521b90202d..f95ddfee41 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -42,6 +42,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate); match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate); match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize); + match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(Beatmap.BaseDifficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(Beatmap.Length); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(Beatmap.BPM); diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index db2803d29a..72d10019b2 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -51,6 +51,9 @@ namespace osu.Game.Screens.Select case "cs": return TryUpdateCriteriaRange(ref criteria.CircleSize, op, value); + case "od": + return TryUpdateCriteriaRange(ref criteria.OverallDifficulty, op, value); + case "bpm": return TryUpdateCriteriaRange(ref criteria.BPM, op, value, 0.01d / 2); From d8117fa73032af9b130c768fd1b1c0a694268580 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:20:34 +0200 Subject: [PATCH 45/73] Add muted objects check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Rulesets/Edit/Checks/CheckMutedObjects.cs | 133 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index d208c7fe07..462a87af85 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Edit // Audio new CheckAudioPresence(), new CheckAudioQuality(), + new CheckMutedObjects(), // Compose new CheckUnsnappedObjects(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs new file mode 100644 index 0000000000..cbe7c7fbab --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -0,0 +1,133 @@ +// 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 osu.Framework.Utils; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckMutedObjects : ICheck + { + /// + /// Volume percentages lower than this are typically inaudible. + /// + private const int muted_threshold = 5; + + /// + /// Volume percentages lower than this can sometimes be inaudible depending on sample used and music volume. + /// + private const int low_volume_threshold = 20; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Low volume hitobjects"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateMutedActive(this), + new IssueTemplateLowVolumeActive(this), + new IssueTemplateMutedPassive(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + foreach (var hitObject in context.Beatmap.HitObjects) + { + // Worth keeping in mind: The samples of an object always play at its end time. + // Objects like spinners have no sound at its start because of this, while hold notes have nested objects to accomplish this. + foreach (var nestedHitObject in hitObject.NestedHitObjects) + { + foreach (var issue in getVolumeIssues(hitObject, nestedHitObject)) + yield return issue; + } + + foreach (var issue in getVolumeIssues(hitObject)) + yield return issue; + } + } + + private IEnumerable getVolumeIssues(HitObject hitObject, HitObject sampledHitObject = null) + { + sampledHitObject ??= hitObject; + if (!sampledHitObject.Samples.Any()) + yield break; + + // Samples that allow themselves to be overridden by control points have a volume of 0. + int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume); + double samplePlayTime = sampledHitObject.GetEndTime(); + + bool head = Precision.AlmostEquals(samplePlayTime, hitObject.StartTime, 1f); + bool tail = Precision.AlmostEquals(samplePlayTime, hitObject.GetEndTime(), 1f); + bool repeat = false; + + if (hitObject is IHasRepeats hasRepeats && !head && !tail) + { + double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + repeat = Precision.AlmostEquals((samplePlayTime - hitObject.StartTime) % spanDuration, 0f, 1f); + } + + // We only care about samples played on the edges of objects, not ones like spinnerspin or slidertick. + if (!head && !tail && !repeat) + yield break; + + string postfix = null; + if (hitObject is IHasDuration) + postfix = head ? "head" : tail ? "tail" : "repeat"; + + if (maxVolume <= muted_threshold) + { + if (head) + yield return new IssueTemplateMutedActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + else + yield return new IssueTemplateMutedPassive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + } + else if (maxVolume <= low_volume_threshold && head) + { + yield return new IssueTemplateLowVolumeActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); + } + } + + public abstract class IssueTemplateMuted : IssueTemplate + { + protected IssueTemplateMuted(ICheck check, IssueType type, string unformattedMessage) + : base(check, type, unformattedMessage) + { + } + + public Issue Create(HitObject hitobject, double volume, double time, string postfix = "") + { + string objectName = hitobject.GetType().Name; + if (!string.IsNullOrEmpty(postfix)) + objectName += " " + postfix; + + return new Issue(hitobject, this, objectName, volume) { Time = time }; + } + } + + public class IssueTemplateMutedActive : IssueTemplateMuted + { + public IssueTemplateMutedActive(ICheck check) + : base(check, IssueType.Problem, "{0} has a volume of {1:0%}. Clickable objects must have clearly audible feedback.") + { + } + } + + public class IssueTemplateLowVolumeActive : IssueTemplateMuted + { + public IssueTemplateLowVolumeActive(ICheck check) + : base(check, IssueType.Warning, "{0} has a volume of {1:0%}, ensure this is audible.") + { + } + } + + public class IssueTemplateMutedPassive : IssueTemplateMuted + { + public IssueTemplateMutedPassive(ICheck check) + : base(check, IssueType.Negligible, "{0} has a volume of {1:0%}, ensure there is no distinct sound here in the song if inaudible.") + { + } + } + } +} From 4b436b774dcb3c35d145410fb56edc12e2c66ebb Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:20:46 +0200 Subject: [PATCH 46/73] Add few hitsounds check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Rulesets/Edit/Checks/CheckFewHitsounds.cs | 161 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 462a87af85..706eec226c 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Edit new CheckAudioPresence(), new CheckAudioQuality(), new CheckMutedObjects(), + new CheckFewHitsounds(), // Compose new CheckUnsnappedObjects(), diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs new file mode 100644 index 0000000000..07ca470e62 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -0,0 +1,161 @@ +// 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 osu.Game.Audio; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckFewHitsounds : ICheck + { + /// + /// 2 measures (4/4) of 120 BPM, typically makes up a few patterns in the map. + /// This is almost always ok, but can still be useful for the mapper to make sure hitsounding coverage is good. + /// + private const int negligible_threshold_time = 4000; + + /// + /// 4 measures (4/4) of 120 BPM, typically makes up a large portion of a section in the song. + /// This is ok if the section is a quiet intro, for example. + /// + private const int warning_threshold_time = 8000; + + /// + /// 12 measures (4/4) of 120 BPM, typically makes up multiple sections in the song. + /// + private const int problem_threshold_time = 24000; + + // Should pass at least this many objects without hitsounds to be considered an issue (should work for Easy diffs too). + private const int warning_threshold_objects = 4; + private const int problem_threshold_objects = 16; + + private static readonly string[] hitsound_types = { HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_FINISH }; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Few or no hitsounds"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateLongPeriodProblem(this), + new IssueTemplateLongPeriodWarning(this), + new IssueTemplateNoHitsounds(this) + }; + + private bool hasHitsounds; + private int objectsWithoutHitsounds; + private double lastHitsoundTime; + + public IEnumerable Run(BeatmapVerifierContext context) + { + if (!context.Beatmap.HitObjects.Any()) + yield break; + + hasHitsounds = false; + objectsWithoutHitsounds = 0; + lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; + + var hitObjectCount = context.Beatmap.HitObjects.Count; + + for (int i = 0; i < hitObjectCount; ++i) + { + var hitObject = context.Beatmap.HitObjects[i]; + + // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). + foreach (var nestedHitObject in hitObject.NestedHitObjects) + { + foreach (var issue in applyHitsoundUpdate(nestedHitObject)) + yield return issue; + } + + // This is used to perform an update at the end so that the period after the last hitsounded object can be an issue. + bool isLastObject = i == hitObjectCount - 1; + + foreach (var issue in applyHitsoundUpdate(hitObject, isLastObject)) + yield return issue; + } + + if (!hasHitsounds) + yield return new IssueTemplateNoHitsounds(this).Create(); + } + + private IEnumerable applyHitsoundUpdate(HitObject hitObject, bool isLastObject = false) + { + var time = hitObject.GetEndTime(); + + // Only generating issues on hitsounded or last objects ensures we get one issue per long period. + // If there are no hitsounds we let the "No hitsounds" template take precedence. + if (hasHitsound(hitObject) || isLastObject && hasHitsounds) + { + var timeWithoutHitsounds = time - lastHitsoundTime; + + if (timeWithoutHitsounds > problem_threshold_time && objectsWithoutHitsounds > problem_threshold_objects) + yield return new IssueTemplateLongPeriodProblem(this).Create(lastHitsoundTime, timeWithoutHitsounds); + else if (timeWithoutHitsounds > warning_threshold_time && objectsWithoutHitsounds > warning_threshold_objects) + yield return new IssueTemplateLongPeriodWarning(this).Create(lastHitsoundTime, timeWithoutHitsounds); + else if (timeWithoutHitsounds > negligible_threshold_time && objectsWithoutHitsounds > warning_threshold_objects) + yield return new IssueTemplateLongPeriodNegligible(this).Create(lastHitsoundTime, timeWithoutHitsounds); + } + + if (hasHitsound(hitObject)) + { + hasHitsounds = true; + objectsWithoutHitsounds = 0; + lastHitsoundTime = time; + } + else if (couldHaveHitsound(hitObject)) + ++objectsWithoutHitsounds; + } + + private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); + private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); + + private bool isHitsound(HitSampleInfo sample) => hitsound_types.Any(sample.Name.Contains); + private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); + + public abstract class IssueTemplateLongPeriod : IssueTemplate + { + protected IssueTemplateLongPeriod(ICheck check, IssueType type) + : base(check, type, "Long period without hitsounds ({0:F1} seconds).") + { + } + + public Issue Create(double time, double duration) => new Issue(this, duration / 1000f) { Time = time }; + } + + public class IssueTemplateLongPeriodProblem : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodProblem(ICheck check) + : base(check, IssueType.Problem) + { + } + } + + public class IssueTemplateLongPeriodWarning : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodWarning(ICheck check) + : base(check, IssueType.Warning) + { + } + } + + public class IssueTemplateLongPeriodNegligible : IssueTemplateLongPeriod + { + public IssueTemplateLongPeriodNegligible(ICheck check) + : base(check, IssueType.Negligible) + { + } + } + + public class IssueTemplateNoHitsounds : IssueTemplate + { + public IssueTemplateNoHitsounds(ICheck check) + : base(check, IssueType.Problem, "There are no hitsounds.") + { + } + + public Issue Create() => new Issue(this); + } + } +} From 7b9569a1176d7a982ef6e9212574a9385c9d665b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:21:01 +0200 Subject: [PATCH 47/73] Add muted object check tests --- .../Editing/Checks/CheckMutedObjectsTest.cs | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs new file mode 100644 index 0000000000..4bfe62a64d --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -0,0 +1,317 @@ +// 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 System.Threading; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckMutedObjectsTest + { + private CheckMutedObjects check; + private ControlPointInfo cpi; + + private const int volume_regular = 50; + private const int volume_low = 15; + private const int volume_muted = 5; + + private sealed class MockNestableHitObject : HitObject, IHasDuration + { + private readonly IEnumerable toBeNested; + + public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) + { + this.toBeNested = toBeNested; + StartTime = startTime; + EndTime = endTime; + } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + foreach (var hitObject in toBeNested) + AddNested(hitObject); + } + + public double EndTime { get; } + + public double Duration + { + get => EndTime - StartTime; + set => throw new System.NotImplementedException(); + } + } + + [SetUp] + public void Setup() + { + check = new CheckMutedObjects(); + + cpi = new ControlPointInfo(); + cpi.Add(0, new SampleControlPoint { SampleVolume = volume_regular }); + cpi.Add(1000, new SampleControlPoint { SampleVolume = volume_low }); + cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted }); + } + + [Test] + public void TestNormalControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { hitcircle }); + } + + [Test] + public void TestLowControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 1000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertLowVolume(new List { hitcircle }); + } + + [Test] + public void TestMutedControlPointVolume() + { + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { hitcircle }); + } + + [Test] + public void TestNormalSampleVolume() + { + // The sample volume should take precedence over the control point volume. + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { hitcircle }); + } + + [Test] + public void TestLowSampleVolume() + { + var hitcircle = new HitCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_low) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertLowVolume(new List { hitcircle }); + } + + [Test] + public void TestMutedSampleVolume() + { + var hitcircle = new HitCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } + }; + hitcircle.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { hitcircle }); + } + + [Test] + public void TestNormalSampleVolumeSlider() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick", volume: volume_muted) } // Should be fine. + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertOk(new List { slider }); + } + + [Test] + public void TestMutedSampleVolumeSliderHead() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } // Applies to the tail. + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { slider }); + } + + [Test] + public void TestMutedSampleVolumeSliderTail() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_muted) } // Applies to the tail. + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMutedPassive(new List { slider }); + } + + [Test] + public void TestMutedControlPointVolumeSliderHead() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 2000, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 2250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 2000, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMuted(new List { slider }); + } + + [Test] + public void TestMutedControlPointVolumeSliderTail() + { + var sliderHead = new SliderHeadCircle + { + StartTime = 0, + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty()); + + var sliderTick = new SliderTick + { + StartTime = 250, + Samples = new List { new HitSampleInfo("slidertick") } + }; + sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty()); + + // Ends after the 5% control point. + var slider = new MockNestableHitObject(new List { sliderHead, sliderTick, }, startTime: 0, endTime: 2500) + { + Samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }; + slider.ApplyDefaults(cpi, new BeatmapDifficulty()); + + assertMutedPassive(new List { slider }); + } + + private void assertOk(List hitObjects) + { + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); + } + + private void assertLowVolume(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckMutedObjects.IssueTemplateLowVolumeActive)); + } + + private void assertMuted(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckMutedObjects.IssueTemplateMutedActive)); + } + + private void assertMutedPassive(List hitObjects) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Any(issue => issue.Template is CheckMutedObjects.IssueTemplateMutedPassive)); + } + + private BeatmapVerifierContext getContext(List hitObjects) + { + var beatmap = new Beatmap + { + ControlPointInfo = cpi, + HitObjects = hitObjects + }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} From a5abc664f30b25c249cb39e50ee67067eccebfb7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:21:15 +0200 Subject: [PATCH 48/73] Add few hitsounds check tests --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs new file mode 100644 index 0000000000..8ae8cd8163 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -0,0 +1,166 @@ +// 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.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckFewHitsoundsTest + { + private CheckFewHitsounds check; + + [SetUp] + public void Setup() + { + check = new CheckFewHitsounds(); + } + + [Test] + public void TestHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 16; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if ((i + 1) % 2 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); + if ((i + 1) % 3 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + if ((i + 1) % 4 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertOk(hitObjects); + } + + [Test] + public void TestLightlyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 30; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i % 8 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertLongPeriodNegligible(hitObjects, count: 3); + } + + [Test] + public void TestRarelyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 30; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i == 0 || i == 15) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one warning between 1st and 11th, and another between 11th and 20th. + assertLongPeriodWarning(hitObjects, count: 2); + } + + [Test] + public void TestExtremelyRarelyHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 80; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if (i == 40) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + assertLongPeriodProblem(hitObjects, count: 2); + } + + [Test] + public void TestNotHitsounded() + { + var hitObjects = new List(); + + for (int i = 0; i < 20; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + assertNoHitsounds(hitObjects); + } + + private void assertOk(List hitObjects) + { + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); + } + + private void assertLongPeriodProblem(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodProblem)); + } + + private void assertLongPeriodWarning(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodWarning)); + } + + private void assertLongPeriodNegligible(List hitObjects, int count = 1) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckFewHitsounds.IssueTemplateLongPeriodNegligible)); + } + + private void assertNoHitsounds(List hitObjects) + { + var issues = check.Run(getContext(hitObjects)).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Any(issue => issue.Template is CheckFewHitsounds.IssueTemplateNoHitsounds)); + } + + private BeatmapVerifierContext getContext(List hitObjects) + { + var beatmap = new Beatmap { HitObjects = hitObjects }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + } + } +} From 82b64f5589a950b43afc9dc9d6b0e02c548e285c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 19:57:12 +0200 Subject: [PATCH 49/73] Add hitsounded with break test --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 8ae8cd8163..8edc6d096e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -47,6 +47,31 @@ namespace osu.Game.Tests.Editing.Checks assertOk(hitObjects); } + [Test] + public void TestHitsoundedWithBreak() + { + var hitObjects = new List(); + + for (int i = 0; i < 32; ++i) + { + var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + + if ((i + 1) % 2 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP)); + if ((i + 1) % 3 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + if ((i + 1) % 4 == 0) + samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH)); + // Leaves a gap in which no hitsounds exist or can be added, and so shouldn't be an issue. + if (i > 8 && i < 24) + continue; + + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); + } + + assertOk(hitObjects); + } + [Test] public void TestLightlyHitsounded() { From 51888d0d5a3fbbcf8fbd4787ffa58b9f3d7b56e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 19:27:34 +0200 Subject: [PATCH 50/73] Rename test methods --- .../Visual/Online/TestSceneBeatmapListingOverlay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index cd382c2bb2..16122e496f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestNonSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithoutResults() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupportUseSupporterOnlyFiltersPlaceholderNoBeatmaps() + public void TestUserWithSupporterUsesSupporterOnlyFiltersWithoutResults() { AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestNonSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithResults() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSupporterUseSupporterOnlyFiltersPlaceholderOneBeatmap() + public void TestUserWithSupporterUsesSupporterOnlyFiltersWithResults() { AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); From b7c4fe2052a38f30aa1789f8c8b9103660b51b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:24:12 +0200 Subject: [PATCH 51/73] Rewrite test helpers to also handle clearing filters --- .../Online/TestSceneBeatmapListingOverlay.cs | 100 +++++++++--------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 16122e496f..66e7248207 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -14,6 +15,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -77,27 +79,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - // test non-supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); notFoundPlaceholderShown(); - // test non-supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); - // test non-supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both RankAchieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); } @@ -107,27 +109,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - // test supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); notFoundPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); notFoundPlaceholderShown(); - // test supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); notFoundPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); - // test supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); notFoundPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); notFoundPlaceholderShown(); } @@ -137,27 +139,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as non-supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = false); - // test non-supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); noPlaceholderShown(); - // test non-supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); - // test non-supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); supporterRequiredPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); } @@ -167,27 +169,27 @@ namespace osu.Game.Tests.Visual.Online AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("set dummy as supporter", () => ((DummyAPIAccess)API).LocalUser.Value.IsSupporter = true); - // test supporter on Rank Achieved filter - toggleRankFilter(Scoring.ScoreRank.XH); + // only Rank Achieved filter + setRankAchievedFilter(new[] { ScoreRank.XH }); noPlaceholderShown(); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); noPlaceholderShown(); - // test supporter on Played filter - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // only Played filter + setPlayedFilter(SearchPlayed.Played); noPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); - // test supporter on both Rank Achieved and Played filter - toggleRankFilter(Scoring.ScoreRank.XH); - toggleSupporterOnlyPlayedFilter(SearchPlayed.Played); + // both Rank Achieved and Played filters + setRankAchievedFilter(new[] { ScoreRank.XH }); + setPlayedFilter(SearchPlayed.Played); noPlaceholderShown(); - AddStep("Set Played filter to Any", () => searchControl.Played.Value = SearchPlayed.Any); - AddStep("Clear Rank Achieved filter", () => searchControl.Ranks.Clear()); + setRankAchievedFilter(Array.Empty()); + setPlayedFilter(SearchPlayed.Any); noPlaceholderShown(); } @@ -200,18 +202,18 @@ namespace osu.Game.Tests.Visual.Online searchControl.Query.TriggerChange(); } - private void toggleRankFilter(Scoring.ScoreRank rank) + private void setRankAchievedFilter(ScoreRank[] ranks) { - AddStep("toggle Rank Achieved filter", () => + AddStep($"set Rank Achieved filter to [{string.Join(',', ranks)}]", () => { searchControl.Ranks.Clear(); - searchControl.Ranks.Add(rank); + searchControl.Ranks.AddRange(ranks); }); } - private void toggleSupporterOnlyPlayedFilter(SearchPlayed played) + private void setPlayedFilter(SearchPlayed played) { - AddStep("toggle Played filter", () => searchControl.Played.Value = played); + AddStep($"set Played filter to {played}", () => searchControl.Played.Value = played); } private void supporterRequiredPlaceholderShown() From 709e555566a80bcc9a7623c6721c99c645da2e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:27:15 +0200 Subject: [PATCH 52/73] Rename test steps for legibility --- .../Visual/Online/TestSceneBeatmapListingOverlay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 66e7248207..5bfb676f81 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -218,17 +218,19 @@ namespace osu.Game.Tests.Visual.Online private void supporterRequiredPlaceholderShown() { - AddUntilStep("supporter-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("\"supporter required\" placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } private void notFoundPlaceholderShown() { - AddUntilStep("not-found-placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + AddUntilStep("\"no maps found\" placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } private void noPlaceholderShown() { - AddUntilStep("no placeholder shown", () => !overlay.ChildrenOfType().Any() && !overlay.ChildrenOfType().Any()); + AddUntilStep("no placeholder shown", () => + !overlay.ChildrenOfType().Any() + && !overlay.ChildrenOfType().Any()); } private class TestAPIBeatmapSet : APIBeatmapSet From b56dd7ff25db96dbcd73c1e0f651987bda726e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:31:26 +0200 Subject: [PATCH 53/73] Fix naming and xmldocs in new beatmap search result structures --- .../BeatmapListingFilterControl.cs | 43 +++++++++++++------ osu.Game/Overlays/BeatmapListingOverlay.cs | 4 +- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index b6a0846407..d80ef075e9 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -26,8 +26,6 @@ namespace osu.Game.Overlays.BeatmapListing { /// /// Fired when a search finishes. - /// SearchFinished.Type = ResultsReturned when results returned. Contains only new items in the case of pagination. - /// SearchFinished.Type = SupporterOnlyFilter when a non-supporter user applied supporter-only filters. /// public Action SearchFinished; @@ -216,7 +214,7 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse = response; getSetsRequest = null; - // check if an non-supporter user used supporter-only filters + // check if a non-supporter used supporter-only filters if (!api.LocalUser.Value.IsSupporter) { List filters = new List(); @@ -229,7 +227,7 @@ namespace osu.Game.Overlays.BeatmapListing if (filters.Any()) { - SearchFinished?.Invoke(SearchResult.SupporterOnlyFilter(filters)); + SearchFinished?.Invoke(SearchResult.SupporterOnlyFilters(filters)); return; } } @@ -260,21 +258,40 @@ namespace osu.Game.Overlays.BeatmapListing base.Dispose(isDisposing); } + /// + /// Indicates the type of result of a user-requested beatmap search. + /// public enum SearchResultType { - // returned with Results + /// + /// Actual results have been returned from API. + /// ResultsReturned, - // non-supporter user applied supporter-only filters - SupporterOnlyFilter + + /// + /// The user is not a supporter, but used supporter-only search filters. + /// + SupporterOnlyFilters } - // Results only valid when Type == ResultsReturned - // Filters only valid when Type == SupporterOnlyFilter + /// + /// Describes the result of a user-requested beatmap search. + /// public struct SearchResult { public SearchResultType Type { get; private set; } + + /// + /// Contains the beatmap sets returned from API. + /// Valid for read if and only if is . + /// public List Results { get; private set; } - public List Filters { get; private set; } + + /// + /// Contains the names of supporter-only filters requested by the user. + /// Valid for read if and only if is . + /// + public List SupporterOnlyFiltersUsed { get; private set; } public static SearchResult ResultsReturned(List results) => new SearchResult { @@ -282,10 +299,10 @@ namespace osu.Game.Overlays.BeatmapListing Results = results }; - public static SearchResult SupporterOnlyFilter(List filters) => new SearchResult + public static SearchResult SupporterOnlyFilters(List filters) => new SearchResult { - Type = SearchResultType.SupporterOnlyFilter, - Filters = filters + Type = SearchResultType.SupporterOnlyFilters, + SupporterOnlyFiltersUsed = filters }; } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index c2ba3d5bc0..5489f0277f 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -123,9 +123,9 @@ namespace osu.Game.Overlays private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { // non-supporter user used supporter-only filters - if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilter) + if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { - supporterRequiredContent.UpdateText(searchResult.Filters); + supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); addContentToPlaceholder(supporterRequiredContent); return; } From 9061ab0a278e058704db7dd4c2ffa6ea476463d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Jun 2021 20:40:54 +0200 Subject: [PATCH 54/73] Update/reword comments in listing overlay --- osu.Game/Overlays/BeatmapListingOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 5489f0277f..460b4ba4c9 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -122,7 +122,6 @@ namespace osu.Game.Overlays private void onSearchFinished(BeatmapListingFilterControl.SearchResult searchResult) { - // non-supporter user used supporter-only filters if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) { supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); @@ -185,7 +184,7 @@ namespace osu.Game.Overlays if (lastContent == notFoundContent || lastContent == supporterRequiredContent) { - // the placeholder may be used multiple times, so don't expire/dispose it. + // the placeholders may be used multiple times, so don't expire/dispose them. transform.Schedule(() => panelTarget.Remove(lastContent)); } else @@ -253,7 +252,8 @@ namespace osu.Game.Overlays } } - // using string literals as there's no proper processing for LocalizeStrings yet + // TODO: localisation requires Text/LinkFlowContainer support for localising strings with links inside + // (https://github.com/ppy/osu-framework/issues/4530) public class SupporterRequiredDrawable : CompositeDrawable { private LinkFlowContainer supporterRequiredText; From 51147405c59e7c01fd035efa55f8ac68fadd755d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 20:44:39 +0200 Subject: [PATCH 55/73] Make || and && priority explicit --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 07ca470e62..f9897ea20c 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Edit.Checks // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || isLastObject && hasHitsounds) + if (hasHitsound(hitObject) || (isLastObject && hasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; From f78cc9397eff96d67f4b198ffd758934ba8b09c3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 20:45:31 +0200 Subject: [PATCH 56/73] Factor out edge type logic --- .../Rulesets/Edit/Checks/CheckMutedObjects.cs | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index cbe7c7fbab..23d89a2f44 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -22,6 +22,14 @@ namespace osu.Game.Rulesets.Edit.Checks /// private const int low_volume_threshold = 20; + private enum EdgeType + { + Head, + Repeat, + Tail, + None + } + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Low volume hitobjects"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -58,37 +66,43 @@ namespace osu.Game.Rulesets.Edit.Checks int maxVolume = sampledHitObject.Samples.Max(sample => sample.Volume > 0 ? sample.Volume : sampledHitObject.SampleControlPoint.SampleVolume); double samplePlayTime = sampledHitObject.GetEndTime(); - bool head = Precision.AlmostEquals(samplePlayTime, hitObject.StartTime, 1f); - bool tail = Precision.AlmostEquals(samplePlayTime, hitObject.GetEndTime(), 1f); - bool repeat = false; - - if (hitObject is IHasRepeats hasRepeats && !head && !tail) - { - double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); - repeat = Precision.AlmostEquals((samplePlayTime - hitObject.StartTime) % spanDuration, 0f, 1f); - } - + EdgeType edgeType = getEdgeAtTime(hitObject, samplePlayTime); // We only care about samples played on the edges of objects, not ones like spinnerspin or slidertick. - if (!head && !tail && !repeat) + if (edgeType == EdgeType.None) yield break; - string postfix = null; - if (hitObject is IHasDuration) - postfix = head ? "head" : tail ? "tail" : "repeat"; + string postfix = hitObject is IHasDuration ? edgeType.ToString().ToLower() : null; if (maxVolume <= muted_threshold) { - if (head) + if (edgeType == EdgeType.Head) yield return new IssueTemplateMutedActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); else yield return new IssueTemplateMutedPassive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); } - else if (maxVolume <= low_volume_threshold && head) + else if (maxVolume <= low_volume_threshold && edgeType == EdgeType.Head) { yield return new IssueTemplateLowVolumeActive(this).Create(hitObject, maxVolume / 100f, sampledHitObject.GetEndTime(), postfix); } } + private EdgeType getEdgeAtTime(HitObject hitObject, double time) + { + if (Precision.AlmostEquals(time, hitObject.StartTime, 1f)) + return EdgeType.Head; + if (Precision.AlmostEquals(time, hitObject.GetEndTime(), 1f)) + return EdgeType.Tail; + + if (hitObject is IHasRepeats hasRepeats) + { + double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + if (Precision.AlmostEquals((time - hitObject.StartTime) % spanDuration, 0f, 1f)) + return EdgeType.Repeat; + } + + return EdgeType.None; + } + public abstract class IssueTemplateMuted : IssueTemplate { protected IssueTemplateMuted(ICheck check, IssueType type, string unformattedMessage) From 5642d321b70f55550b4bc992c0ccf992514cf5b9 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:43:08 +0200 Subject: [PATCH 57/73] Fix comments in few hitsounds check tests --- osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 8edc6d096e..7561e94d2e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one warning between 1st and 11th, and another between 11th and 20th. + // Should prompt one warning between 1st and 16th, and another between 16th and 31st. assertLongPeriodWarning(hitObjects, count: 2); } @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one problem between 1st and 40th, and another between 40th and 80th. + // Should prompt one problem between 1st and 41st, and another between 41st and 81st. assertLongPeriodProblem(hitObjects, count: 2); } @@ -140,7 +140,6 @@ namespace osu.Game.Tests.Editing.Checks hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } - // Should prompt one problem between 1st and 40th, and another between 40th and 80th. assertNoHitsounds(hitObjects); } From 191308434215634a96f88dc3501bacee9a2da354 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:48:28 +0200 Subject: [PATCH 58/73] Use `HitSampleInfo.AllAdditions` instead of new list --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index f9897ea20c..acdac83141 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -32,8 +32,6 @@ namespace osu.Game.Rulesets.Edit.Checks private const int warning_threshold_objects = 4; private const int problem_threshold_objects = 16; - private static readonly string[] hitsound_types = { HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_FINISH }; - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Few or no hitsounds"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -111,7 +109,7 @@ namespace osu.Game.Rulesets.Edit.Checks private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); - private bool isHitsound(HitSampleInfo sample) => hitsound_types.Any(sample.Name.Contains); + private bool isHitsound(HitSampleInfo sample) => HitSampleInfo.AllAdditions.Any(sample.Name.Contains); private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); public abstract class IssueTemplateLongPeriod : IssueTemplate From d29e6f46953624bbfac6dbea4f5c0929a0071ab6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:49:06 +0200 Subject: [PATCH 59/73] Add negligible template to `PossibleTemplates` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index acdac83141..1de3652a39 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Edit.Checks { new IssueTemplateLongPeriodProblem(this), new IssueTemplateLongPeriodWarning(this), + new IssueTemplateLongPeriodNegligible(this), new IssueTemplateNoHitsounds(this) }; From 5bc08ebadb95ff4c215a9f7214dfde7d66a11031 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 26 Jun 2021 23:49:25 +0200 Subject: [PATCH 60/73] Rename `hasHitsounds` -> `mapHasHitsounds` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 1de3652a39..8d8243938a 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateNoHitsounds(this) }; - private bool hasHitsounds; + private bool mapHasHitsounds; private int objectsWithoutHitsounds; private double lastHitsoundTime; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (!context.Beatmap.HitObjects.Any()) yield break; - hasHitsounds = false; + mapHasHitsounds = false; objectsWithoutHitsounds = 0; lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Edit.Checks yield return issue; } - if (!hasHitsounds) + if (!mapHasHitsounds) yield return new IssueTemplateNoHitsounds(this).Create(); } @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Edit.Checks // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || (isLastObject && hasHitsounds)) + if (hasHitsound(hitObject) || (isLastObject && mapHasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hasHitsound(hitObject)) { - hasHitsounds = true; + mapHasHitsounds = true; objectsWithoutHitsounds = 0; lastHitsoundTime = time; } From 4796b1b2089f6e5c64449bbb01e81182231491fd Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 00:04:30 +0200 Subject: [PATCH 61/73] Use local variables for `hasHitsound` & `couldHaveHitsound` --- osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 8d8243938a..1fca6b26a4 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -82,10 +82,12 @@ namespace osu.Game.Rulesets.Edit.Checks private IEnumerable applyHitsoundUpdate(HitObject hitObject, bool isLastObject = false) { var time = hitObject.GetEndTime(); + bool hasHitsound = hitObject.Samples.Any(isHitsound); + bool couldHaveHitsound = hitObject.Samples.Any(isHitnormal); // Only generating issues on hitsounded or last objects ensures we get one issue per long period. // If there are no hitsounds we let the "No hitsounds" template take precedence. - if (hasHitsound(hitObject) || (isLastObject && mapHasHitsounds)) + if (hasHitsound || (isLastObject && mapHasHitsounds)) { var timeWithoutHitsounds = time - lastHitsoundTime; @@ -97,19 +99,16 @@ namespace osu.Game.Rulesets.Edit.Checks yield return new IssueTemplateLongPeriodNegligible(this).Create(lastHitsoundTime, timeWithoutHitsounds); } - if (hasHitsound(hitObject)) + if (hasHitsound) { mapHasHitsounds = true; objectsWithoutHitsounds = 0; lastHitsoundTime = time; } - else if (couldHaveHitsound(hitObject)) + else if (couldHaveHitsound) ++objectsWithoutHitsounds; } - private bool hasHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitsound); - private bool couldHaveHitsound(HitObject hitObject) => hitObject.Samples.Any(isHitnormal); - private bool isHitsound(HitSampleInfo sample) => HitSampleInfo.AllAdditions.Any(sample.Name.Contains); private bool isHitnormal(HitSampleInfo sample) => sample.Name.Contains(HitSampleInfo.HIT_NORMAL); From 0c0fd291d9381d718668e0588d6fb5dee60e91c7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 01:25:03 +0200 Subject: [PATCH 62/73] Order hitobjects by endtime --- .../Rulesets/Edit/Checks/CheckFewHitsounds.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs index 1fca6b26a4..5185ba6c99 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFewHitsounds.cs @@ -55,18 +55,23 @@ namespace osu.Game.Rulesets.Edit.Checks objectsWithoutHitsounds = 0; lastHitsoundTime = context.Beatmap.HitObjects.First().StartTime; - var hitObjectCount = context.Beatmap.HitObjects.Count; + var hitObjectsIncludingNested = new List(); + + foreach (var hitObject in context.Beatmap.HitObjects) + { + // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). + foreach (var nestedHitObject in hitObject.NestedHitObjects) + hitObjectsIncludingNested.Add(nestedHitObject); + + hitObjectsIncludingNested.Add(hitObject); + } + + var hitObjectsByEndTime = hitObjectsIncludingNested.OrderBy(o => o.GetEndTime()).ToList(); + var hitObjectCount = hitObjectsByEndTime.Count; for (int i = 0; i < hitObjectCount; ++i) { - var hitObject = context.Beatmap.HitObjects[i]; - - // Samples play on the end of objects. Some objects have nested objects to accomplish playing them elsewhere (e.g. slider head/repeat). - foreach (var nestedHitObject in hitObject.NestedHitObjects) - { - foreach (var issue in applyHitsoundUpdate(nestedHitObject)) - yield return issue; - } + var hitObject = hitObjectsByEndTime[i]; // This is used to perform an update at the end so that the period after the last hitsounded object can be an issue. bool isLastObject = i == hitObjectCount - 1; From 2cd7eda3c4d62e7b7898c7a60de3d78ea5447b5f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 02:30:12 +0200 Subject: [PATCH 63/73] Add "or equal to" to volume threshold xmldocs --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 23d89a2f44..2ac1efc636 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Edit.Checks public class CheckMutedObjects : ICheck { /// - /// Volume percentages lower than this are typically inaudible. + /// Volume percentages lower than or equal to this are typically inaudible. /// private const int muted_threshold = 5; /// - /// Volume percentages lower than this can sometimes be inaudible depending on sample used and music volume. + /// Volume percentages lower than or equal to this can sometimes be inaudible depending on sample used and music volume. /// private const int low_volume_threshold = 20; From 4cfa0ae5ec79bf2e1922132f23515baa374f2b4e Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 03:26:35 +0200 Subject: [PATCH 64/73] Improve precision for repeat edges --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 2ac1efc636..c743b5693e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Utils; @@ -96,7 +97,9 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitObject is IHasRepeats hasRepeats) { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); - if (Precision.AlmostEquals((time - hitObject.StartTime) % spanDuration, 0f, 1f)) + double spans = (time - hitObject.StartTime) / spanDuration; + + if (Precision.AlmostEquals(spans, Math.Ceiling(spans)) || Precision.AlmostEquals(spans, Math.Floor(spans))) return EdgeType.Repeat; } From d1f852d102489ae8ae69069dc5ae1416ea0927da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 13:06:20 +0900 Subject: [PATCH 65/73] Make `Populate` abstract to avoid unnecessary base call async complexity --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 4 ++++ osu.Game/Skinning/SkinManager.cs | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 8efd451857..c1a4a6e18a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -727,7 +727,7 @@ namespace osu.Game.Database /// The model to populate. /// The archive to use as a reference for population. May be null. /// An optional cancellation token. - protected virtual Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default) => Task.CompletedTask; + protected abstract Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default); /// /// Perform any final actions before the import to database executes. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9d3b952ada..d5bea0affc 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Linq.Expressions; using System.Threading; +using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; @@ -72,6 +73,9 @@ namespace osu.Game.Scoring } } + protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + => Task.CompletedTask; + protected override void ExportModelTo(ScoreInfo model, Stream outputStream) { var file = model.Files.SingleOrDefault(); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 4cde4cd2b8..645c943d09 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -142,16 +142,16 @@ namespace osu.Game.Skinning return base.ComputeHash(item, reader); } - protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) + protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { - await base.Populate(model, archive, cancellationToken).ConfigureAwait(false); - var instance = GetSkin(model); model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo(); if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) populateMetadata(model, instance); + + return Task.CompletedTask; } private void populateMetadata(SkinInfo item, Skin instance) From 46f8100f4371b64e2bda9b484493597b4a868bf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Jun 2021 13:06:47 +0900 Subject: [PATCH 66/73] Remove overly verbose logging during beatmap imports --- osu.Game/Beatmaps/BeatmapManager.cs | 2 -- osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 00af06703d..86c8fb611f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -191,8 +191,6 @@ namespace osu.Game.Beatmaps { var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); - LogForModel(beatmapSet, $"Validating online IDs for {beatmapSet.Beatmaps.Count} beatmaps..."); - // ensure all IDs are unique if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) { diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 5dff4fe282..7824205257 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -48,7 +48,6 @@ namespace osu.Game.Beatmaps public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) { - LogForModel(beatmapSet, "Performing online lookups..."); return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray()); } From c2ceb83bbb20c53edff6c4973fb45e48a984cad2 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:16:40 +0200 Subject: [PATCH 67/73] Move `MockNestedHitObject` to own class --- .../Editing/Checks/CheckMutedObjectsTest.cs | 28 --------------- .../Editing/Checks/MockNestableHitObject.cs | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs index 4bfe62a64d..41a8f72305 100644 --- a/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckMutedObjectsTest.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using System.Threading; using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -11,7 +10,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; @@ -27,32 +25,6 @@ namespace osu.Game.Tests.Editing.Checks private const int volume_low = 15; private const int volume_muted = 5; - private sealed class MockNestableHitObject : HitObject, IHasDuration - { - private readonly IEnumerable toBeNested; - - public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) - { - this.toBeNested = toBeNested; - StartTime = startTime; - EndTime = endTime; - } - - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) - { - foreach (var hitObject in toBeNested) - AddNested(hitObject); - } - - public double EndTime { get; } - - public double Duration - { - get => EndTime - StartTime; - set => throw new System.NotImplementedException(); - } - } - [SetUp] public void Setup() { diff --git a/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs b/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs new file mode 100644 index 0000000000..29938839d3 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/MockNestableHitObject.cs @@ -0,0 +1,36 @@ +// 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.Threading; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Tests.Editing.Checks +{ + public sealed class MockNestableHitObject : HitObject, IHasDuration + { + private readonly IEnumerable toBeNested; + + public MockNestableHitObject(IEnumerable toBeNested, double startTime, double endTime) + { + this.toBeNested = toBeNested; + StartTime = startTime; + EndTime = endTime; + } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + foreach (var hitObject in toBeNested) + AddNested(hitObject); + } + + public double EndTime { get; } + + public double Duration + { + get => EndTime - StartTime; + set => throw new System.NotImplementedException(); + } + } +} From 1d5bff166043e0b7b41c98603c65af3e411ec115 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:26:52 +0200 Subject: [PATCH 68/73] Add concurrent hitobjects test for few hitsounds check See https://github.com/ppy/osu/pull/13669#discussion_r659314980 --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 7561e94d2e..d681c3fd3d 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; @@ -143,6 +144,35 @@ namespace osu.Game.Tests.Editing.Checks assertNoHitsounds(hitObjects); } + [Test] + public void TestConcurrentObjects() + { + var hitObjects = new List(); + + var notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + var hitsounded = new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL), + new HitSampleInfo(HitSampleInfo.HIT_FINISH) + }; + + var ticks = new List(); + for (int i = 1; i < 10; ++i) + ticks.Add(new SliderTick { StartTime = 5000 * i, Samples = hitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 50000) + { + Samples = notHitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + hitObjects.Add(nested); + + for (int i = 1; i <= 6; ++i) + hitObjects.Add(new HitCircle { StartTime = 10000 * i, Samples = notHitsounded }); + + assertOk(hitObjects); + } + private void assertOk(List hitObjects) { Assert.That(check.Run(getContext(hitObjects)), Is.Empty); From a4a5325b73e8e6108137aacd40dbdf93c074f980 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:39:31 +0200 Subject: [PATCH 69/73] Improve acceptable difference for repeat edges Likelihood that `spanDuration` is greater than E+7 is quite low in any realistic case, so this should work fine. --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index c743b5693e..0559a8b0cd 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -98,9 +98,13 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); double spans = (time - hitObject.StartTime) / spanDuration; + double acceptableDifference = 1 / spanDuration; // 1 ms of acceptable difference, as with head/tail above. - if (Precision.AlmostEquals(spans, Math.Ceiling(spans)) || Precision.AlmostEquals(spans, Math.Floor(spans))) + if (Precision.AlmostEquals(spans, Math.Ceiling(spans), acceptableDifference) || + Precision.AlmostEquals(spans, Math.Floor(spans), acceptableDifference)) + { return EdgeType.Repeat; + } } return EdgeType.None; From 9f9e96ce9ee797024585af8515338f67bc0175d6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:40:09 +0200 Subject: [PATCH 70/73] Add check for `spanDuration` <= 0 prior to division --- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index 0559a8b0cd..a4ff921b7e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -97,6 +97,10 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitObject is IHasRepeats hasRepeats) { double spanDuration = hasRepeats.Duration / hasRepeats.SpanCount(); + if (spanDuration <= 0) + // Prevents undefined behaviour in cases like where zero/negative-length sliders/hold notes exist. + return EdgeType.None; + double spans = (time - hitObject.StartTime) / spanDuration; double acceptableDifference = 1 / spanDuration; // 1 ms of acceptable difference, as with head/tail above. From 1dbac76da5c4366bab40f1962faa40e0322d0c5b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:57:41 +0200 Subject: [PATCH 71/73] Use local variables for common sample lists --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index d681c3fd3d..21e274c2c0 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -20,10 +20,19 @@ namespace osu.Game.Tests.Editing.Checks { private CheckFewHitsounds check; + private List notHitsounded; + private List hitsounded; + [SetUp] public void Setup() { check = new CheckFewHitsounds(); + notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; + hitsounded = new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL), + new HitSampleInfo(HitSampleInfo.HIT_FINISH) + }; } [Test] @@ -80,10 +89,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 30; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i % 8 == 0) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = i % 8 == 0 ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -98,10 +104,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 30; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i == 0 || i == 15) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = (i == 0 || i == 15) ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -117,10 +120,7 @@ namespace osu.Game.Tests.Editing.Checks for (int i = 0; i < 80; ++i) { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - if (i == 40) - samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE)); + var samples = i == 40 ? hitsounded : notHitsounded; hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); } @@ -135,11 +135,7 @@ namespace osu.Game.Tests.Editing.Checks var hitObjects = new List(); for (int i = 0; i < 20; ++i) - { - var samples = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - - hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = samples }); - } + hitObjects.Add(new HitCircle { StartTime = 1000 * i, Samples = notHitsounded }); assertNoHitsounds(hitObjects); } @@ -149,13 +145,6 @@ namespace osu.Game.Tests.Editing.Checks { var hitObjects = new List(); - var notHitsounded = new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }; - var hitsounded = new List - { - new HitSampleInfo(HitSampleInfo.HIT_NORMAL), - new HitSampleInfo(HitSampleInfo.HIT_FINISH) - }; - var ticks = new List(); for (int i = 1; i < 10; ++i) ticks.Add(new SliderTick { StartTime = 5000 * i, Samples = hitsounded }); From b58644106c188d09802dd8d1e455633193651f5d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 27 Jun 2021 15:58:00 +0200 Subject: [PATCH 72/73] Add nested hitobject tests for few hitsounds check --- .../Editing/Checks/CheckFewHitsoundsTest.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs index 21e274c2c0..cf5b3a42a4 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFewHitsoundsTest.cs @@ -140,6 +140,38 @@ namespace osu.Game.Tests.Editing.Checks assertNoHitsounds(hitObjects); } + [Test] + public void TestNestedObjectsHitsounded() + { + var ticks = new List(); + for (int i = 1; i < 16; ++i) + ticks.Add(new SliderTick { StartTime = 1000 * i, Samples = hitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 16000) + { + Samples = hitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertOk(new List { nested }); + } + + [Test] + public void TestNestedObjectsRarelyHitsounded() + { + var ticks = new List(); + for (int i = 1; i < 16; ++i) + ticks.Add(new SliderTick { StartTime = 1000 * i, Samples = i == 0 ? hitsounded : notHitsounded }); + + var nested = new MockNestableHitObject(ticks.ToList(), 0, 16000) + { + Samples = hitsounded + }; + nested.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + assertLongPeriodWarning(new List { nested }); + } + [Test] public void TestConcurrentObjects() { From e4780d4b06f9eb7b2b420114853f9b99b93481c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Jun 2021 15:29:47 +0900 Subject: [PATCH 73/73] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3c4380e355..c845d7f276 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f91620bd25..f047859dbb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 22c4340ba2..304047ad12 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - +