From eba3cfc96e0e812a764cf70103905f4d7aff0874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 12:37:12 +0100 Subject: [PATCH 01/41] Move `ScoreInfo` string representation to extension method --- osu.Game/Scoring/ScoreInfo.cs | 2 +- osu.Game/Scoring/ScoreInfoExtensions.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Scoring/ScoreInfoExtensions.cs diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index e5b050fc01..736a939a59 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -225,7 +225,7 @@ namespace osu.Game.Scoring return clone; } - public override string ToString() => $"{User} playing {BeatmapInfo}"; + public override string ToString() => this.GetDisplayTitle(); public bool Equals(ScoreInfo other) { diff --git a/osu.Game/Scoring/ScoreInfoExtensions.cs b/osu.Game/Scoring/ScoreInfoExtensions.cs new file mode 100644 index 0000000000..2279337fef --- /dev/null +++ b/osu.Game/Scoring/ScoreInfoExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; + +namespace osu.Game.Scoring +{ + public static class ScoreInfoExtensions + { + /// + /// A user-presentable display title representing this score. + /// + public static string GetDisplayTitle(this IScoreInfo scoreInfo) => $"{scoreInfo.User.Username} playing {scoreInfo.Beatmap.GetDisplayTitle()}"; + } +} From a1b55d6490cd103b9d3eb0238305df7a38234a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 13:34:36 +0100 Subject: [PATCH 02/41] Add failing test case --- .../Online/TestSceneBeatmapManager.cs | 52 ++++++++++++++++--- .../Notifications/ProgressNotification.cs | 9 +++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs index 0ae0186770..4d5bee13f2 100644 --- a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs +++ b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Tests.Visual; @@ -16,7 +17,30 @@ namespace osu.Game.Tests.Online private BeatmapManager beatmaps; private ProgressNotification recentNotification; - private static readonly BeatmapSetInfo test_model = new BeatmapSetInfo { OnlineBeatmapSetID = 1 }; + private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo + { + OnlineBeatmapSetID = 1, + Metadata = new BeatmapMetadata + { + Artist = "test author", + Title = "test title", + Author = new APIUser + { + Username = "mapper" + } + } + }; + + private static readonly APIBeatmapSet test_online_model = new APIBeatmapSet + { + OnlineID = 2, + Artist = "test author", + Title = "test title", + Author = new APIUser + { + Username = "mapper" + } + }; [BackgroundDependencyLoader] private void load(BeatmapManager beatmaps) @@ -26,25 +50,41 @@ namespace osu.Game.Tests.Online beatmaps.PostNotification = n => recentNotification = n as ProgressNotification; } + private static readonly object[][] notification_test_cases = + { + new object[] { test_db_model }, + new object[] { test_online_model } + }; + + [TestCaseSource(nameof(notification_test_cases))] + public void TestNotificationMessage(IBeatmapSetInfo model) + { + AddStep("clear recent notification", () => recentNotification = null); + AddStep("download beatmap", () => beatmaps.Download(model)); + + AddUntilStep("wait for notification", () => recentNotification != null); + AddUntilStep("notification text correct", () => recentNotification.Text.ToString() == "Downloading test author - test title (mapper)"); + } + [Test] public void TestCancelDownloadFromRequest() { - AddStep("download beatmap", () => beatmaps.Download(test_model)); + AddStep("download beatmap", () => beatmaps.Download(test_db_model)); - AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_model).Cancel()); + AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_db_model).Cancel()); - AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_model) == null); + AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_db_model) == null); AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled); } [Test] public void TestCancelDownloadFromNotification() { - AddStep("download beatmap", () => beatmaps.Download(test_model)); + AddStep("download beatmap", () => beatmaps.Download(test_db_model)); AddStep("cancel download from notification", () => recentNotification.Close()); - AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_model) == null); + AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_db_model) == null); AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled); } } diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index c44b88ad29..5b74bff817 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -23,9 +23,16 @@ namespace osu.Game.Overlays.Notifications { private const float loading_spinner_size = 22; + private LocalisableString text; + public LocalisableString Text { - set => Schedule(() => textDrawable.Text = value); + get => text; + set + { + text = value; + Schedule(() => textDrawable.Text = text); + } } public string CompletionText { get; set; } = "Task has completed!"; From 5ec8288508876dff6b6af1717b4a9fb6866792f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 13:34:56 +0100 Subject: [PATCH 03/41] Add `GetDisplayString()` extension to handle all model interface types globally --- osu.Game/Database/ArchiveModelManager.cs | 3 +- osu.Game/Database/ModelDownloader.cs | 3 +- osu.Game/Extensions/ModelExtensions.cs | 61 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Extensions/ModelExtensions.cs diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 019749760f..320f108886 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -15,6 +15,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Game.Extensions; using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.IPC; @@ -192,7 +193,7 @@ namespace osu.Game.Database else { notification.CompletionText = imported.Count == 1 - ? $"Imported {imported.First().Value}!" + ? $"Imported {imported.First().Value.GetDisplayString()}!" : $"Imported {imported.Count} {HumanisedModelName}s!"; if (imported.Count > 0 && PostImport != null) diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 3c1f181f24..43ba62dfe0 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Humanizer; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Overlays.Notifications; @@ -50,7 +51,7 @@ namespace osu.Game.Database DownloadNotification notification = new DownloadNotification { - Text = $"Downloading {request.Model}", + Text = $"Downloading {request.Model.GetDisplayString()}", }; request.DownloadProgressed += progress => diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs new file mode 100644 index 0000000000..5c96add076 --- /dev/null +++ b/osu.Game/Extensions/ModelExtensions.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Extensions +{ + public static class ModelExtensions + { + /// + /// Returns a user-facing string representing the . + /// + /// + /// + /// Non-interface types without special handling will fall back to . + /// + /// + /// Warning: This method is _purposefully_ not called GetDisplayTitle() like the others, because otherwise + /// extension method type inference rules cause this method to call itself and cause a stack overflow. + /// + /// + public static string GetDisplayString(this object model) + { + string result = null; + + switch (model) + { + case IBeatmapSetInfo beatmapSetInfo: + result = beatmapSetInfo.Metadata?.GetDisplayTitle(); + break; + + case IBeatmapInfo beatmapInfo: + result = beatmapInfo.GetDisplayTitle(); + break; + + case IBeatmapMetadataInfo metadataInfo: + result = metadataInfo.GetDisplayTitle(); + break; + + case IScoreInfo scoreInfo: + result = scoreInfo.GetDisplayTitle(); + break; + + case IRulesetInfo rulesetInfo: + result = rulesetInfo.Name; + break; + + case IUser user: + result = user.Username; + break; + } + + // fallback in case none of the above happens to match. + result ??= model.ToString(); + return result; + } + } +} From 9686bf507d7b49419e802f44d841579a954536b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 13:48:05 +0100 Subject: [PATCH 04/41] Add failing tests for coverage of `GetDisplayString()` --- osu.Game.Tests/Models/DisplayStringTest.cs | 95 ++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 osu.Game.Tests/Models/DisplayStringTest.cs diff --git a/osu.Game.Tests/Models/DisplayStringTest.cs b/osu.Game.Tests/Models/DisplayStringTest.cs new file mode 100644 index 0000000000..9a3c32bfb2 --- /dev/null +++ b/osu.Game.Tests/Models/DisplayStringTest.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Extensions; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Tests.Models +{ + [TestFixture] + public class DisplayStringTest + { + private static readonly object[][] test_cases = + { + new object[] { makeMockBeatmapSet(), "artist - title (author)" }, + new object[] { makeMockBeatmap(), "artist - title (author) [difficulty]" }, + new object[] { makeMockMetadata(), "artist - title (author)" }, + new object[] { makeMockScore(), "user playing artist - title (author) [difficulty]" }, + new object[] { makeMockRuleset(), "ruleset" }, + new object[] { makeMockUser(), "user" }, + new object[] { new Fallback(), "fallback" } + }; + + [TestCaseSource(nameof(test_cases))] + public void TestDisplayString(object model, string expected) => Assert.That(model.GetDisplayString(), Is.EqualTo(expected)); + + private static IBeatmapSetInfo makeMockBeatmapSet() + { + var mock = new Mock(); + + mock.Setup(m => m.Metadata).Returns(makeMockMetadata); + + return mock.Object; + } + + private static IBeatmapInfo makeMockBeatmap() + { + var mock = new Mock(); + + mock.Setup(m => m.Metadata).Returns(makeMockMetadata); + mock.Setup(m => m.DifficultyName).Returns("difficulty"); + + return mock.Object; + } + + private static IBeatmapMetadataInfo makeMockMetadata() + { + var mock = new Mock(); + + mock.Setup(m => m.Artist).Returns("artist"); + mock.Setup(m => m.Title).Returns("title"); + mock.Setup(m => m.Author.Username).Returns("author"); + + return mock.Object; + } + + private static IScoreInfo makeMockScore() + { + var mock = new Mock(); + + mock.Setup(m => m.User).Returns(new APIUser { Username = "user" }); // TODO: temporary. + mock.Setup(m => m.Beatmap).Returns(makeMockBeatmap); + + return mock.Object; + } + + private static IRulesetInfo makeMockRuleset() + { + var mock = new Mock(); + + mock.Setup(m => m.Name).Returns("ruleset"); + + return mock.Object; + } + + private static IUser makeMockUser() + { + var mock = new Mock(); + + mock.Setup(m => m.Username).Returns("user"); + + return mock.Object; + } + + private class Fallback + { + public override string ToString() => "fallback"; + } + } +} From 3d148aea4040615fa8250a2de890b6e318783633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 13:51:18 +0100 Subject: [PATCH 05/41] Fix `GetDisplayTitle()` implementations relying on `ToString()` themselves --- osu.Game/Beatmaps/BeatmapInfoExtensions.cs | 2 +- osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs index 707b588063..eab66b9857 100644 --- a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps /// /// A user-presentable display title representing this beatmap. /// - public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{beatmapInfo.Metadata} {getVersionString(beatmapInfo)}".Trim(); + public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{beatmapInfo.Metadata.GetDisplayTitle()} {getVersionString(beatmapInfo)}".Trim(); /// /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields. diff --git a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs index 27cd7f8d9a..32fb389e9a 100644 --- a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs @@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps /// public static string GetDisplayTitle(this IBeatmapMetadataInfo metadataInfo) { - string author = string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $"({metadataInfo.Author})"; + string author = string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $"({metadataInfo.Author.Username})"; return $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim(); } From 03a315b9f54595446861e5cd6377efd44f17ffb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 14:33:06 +0100 Subject: [PATCH 06/41] Fix missing beatmap in replay download test scene Was causing nullrefs in `GetDisplayTitle()`. --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index d4036fefc0..f47fae33ca 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Gameplay Id = 39828, Username = @"WubWoofWolf", } - }.CreateScoreInfo(rulesets); + }.CreateScoreInfo(rulesets, CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo); } private class TestReplayDownloadButton : ReplayDownloadButton From 64bc8da14c5b2e22d4ddc36e5644a64359133f5f Mon Sep 17 00:00:00 2001 From: Semyon Rozhkov Date: Tue, 9 Nov 2021 17:11:19 +0300 Subject: [PATCH 07/41] Add "No Scope" mod implementation for Catch --- .../Mods/TestSceneCatchModNoScope.cs | 156 ++++++++++++++++++ osu.Game.Rulesets.Catch/CatchRuleset.cs | 1 + .../Mods/CatchModNoScope.cs | 38 +++++ osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 72 +------- osu.Game/Rulesets/Mods/ModNoScope.cs | 75 +++++++++ 5 files changed, 275 insertions(+), 67 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs create mode 100644 osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs create mode 100644 osu.Game/Rulesets/Mods/ModNoScope.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs new file mode 100644 index 0000000000..5d8bbad384 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -0,0 +1,156 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Mods +{ + public class TestSceneCatchModNoScope : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + + [Test] + public void TestVisibleDuringBreak() + { + CreateModTest(new ModTestData + { + Mod = new CatchModNoScope + { + HiddenComboCount = { Value = 0 }, + }, + Autoplay = true, + PassCondition = () => true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 1000, + }, + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 5000, + } + }, + Breaks = new List + { + new BreakPeriod(2000, 4000), + } + } + }); + + AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); + AddUntilStep("wait for start of break", isBreak); + AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); + AddUntilStep("wait for end of break", () => !isBreak()); + AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); + } + + [Test] + public void TestVisibleDuringBananaShower() + { + CreateModTest(new ModTestData + { + Mod = new CatchModNoScope + { + HiddenComboCount = { Value = 0 }, + }, + Autoplay = true, + PassCondition = () => true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 1000, + }, + new BananaShower + { + StartTime = 2000, + Duration = 2000, + }, + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 5000, + } + } + } + }); + + AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); + AddUntilStep("wait for start of banana shower", isBananaShower); + AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); + AddUntilStep("wait for end of banana shower", () => !isBananaShower()); + AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); + } + + [Test] + public void TestVisibleAfterComboBreak() + { + CreateModTest(new ModTestData + { + Mod = new CatchModNoScope + { + HiddenComboCount = { Value = 2 }, + }, + Autoplay = true, + PassCondition = () => true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = 0, + StartTime = 1000, + }, + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 3000, + }, + new Fruit + { + X = CatchPlayfield.WIDTH, + StartTime = 5000, + }, + } + } + }); + + AddAssert("catcher must start visible", () => catcherAlphaAlmostEquals(1)); + AddUntilStep("wait for combo", () => Player.ScoreProcessor.Combo.Value >= 2); + AddAssert("catcher must dim after combo", () => !catcherAlphaAlmostEquals(1)); + AddStep("break combo", () => Player.ScoreProcessor.Combo.Set(0)); + AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); + } + + private bool isBananaShower() => Player.ChildrenOfType().SingleOrDefault() != null; + + private bool isBreak() => Player.IsBreakTime.Value; + + private bool catcherAlphaAlmostEquals(float alpha) + { + var playfield = (CatchPlayfield)Player.DrawableRuleset.Playfield; + return Precision.AlmostEquals(playfield.CatcherArea.Alpha, alpha); + } + } +} diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9fee6b2bc1..c256172177 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -133,6 +133,7 @@ namespace osu.Game.Rulesets.Catch new MultiMod(new ModWindUp(), new ModWindDown()), new CatchModFloatingFruits(), new CatchModMuted(), + new CatchModNoScope(), }; default: diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs new file mode 100644 index 0000000000..7b55150582 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -0,0 +1,38 @@ +// 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.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Framework.Utils; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Utils; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModNoScope : ModNoScope, IApplicableToBeatmap, IApplicableToDrawableRuleset + { + public override string Description => "Where's the catcher?"; + + public PeriodTracker BananaShowerPeriods; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + BananaShowerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); + } + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + var playfield = (CatchPlayfield)drawableRuleset.Playfield; + playfield.OnUpdate += _ => + { + bool shouldAlwaysShowCatcher = IsBreakTime.Value || BananaShowerPeriods.IsInAny(playfield.Clock.CurrentTime); + float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha; + playfield.CatcherArea.Alpha = (float)Interpolation.Lerp(playfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index 501c0a55bd..3f9cd098cd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -3,93 +3,31 @@ using System; using System.Linq; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Screens.Play; using osu.Game.Utils; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor, IApplicableToPlayer, IApplicableToBeatmap + public class OsuModNoScope : ModNoScope, IUpdatableByPlayfield, IApplicableToBeatmap { - /// - /// Slightly higher than the cutoff for . - /// - private const float min_alpha = 0.0002f; - - private const float transition_duration = 100; - - public override string Name => "No Scope"; - public override string Acronym => "NS"; - public override ModType Type => ModType.Fun; - public override IconUsage? Icon => FontAwesome.Solid.EyeSlash; public override string Description => "Where's the cursor?"; - public override double ScoreMultiplier => 1; - private BindableNumber currentCombo; - private IBindable isBreakTime; private PeriodTracker spinnerPeriods; - private float comboBasedAlpha; - - [SettingSource( - "Hidden at combo", - "The combo count at which the cursor becomes completely hidden", - SettingControlType = typeof(SettingsSlider) - )] - public BindableInt HiddenComboCount { get; } = new BindableInt - { - Default = 10, - Value = 10, - MinValue = 0, - MaxValue = 50, - }; - - public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; - - public void ApplyToPlayer(Player player) - { - isBreakTime = player.IsBreakTime.GetBoundCopy(); - } - public void ApplyToBeatmap(IBeatmap beatmap) { - spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - transition_duration, b.EndTime))); - } - - public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) - { - if (HiddenComboCount.Value == 0) return; - - currentCombo = scoreProcessor.Combo.GetBoundCopy(); - currentCombo.BindValueChanged(combo => - { - comboBasedAlpha = Math.Max(min_alpha, 1 - (float)combo.NewValue / HiddenComboCount.Value); - }, true); + spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); } public virtual void Update(Playfield playfield) { - bool shouldAlwaysShowCursor = isBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime); - float targetAlpha = shouldAlwaysShowCursor ? 1 : comboBasedAlpha; - playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / transition_duration, 0, 1)); + bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime); + float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha; + playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } } - - public class HiddenComboSlider : OsuSliderBar - { - public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText; - } } diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs new file mode 100644 index 0000000000..9e2734b775 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModNoScope : Mod, IApplicableToScoreProcessor, IApplicableToPlayer + { + public override string Name => "No Scope"; + public override string Acronym => "NS"; + public override ModType Type => ModType.Fun; + public override IconUsage? Icon => FontAwesome.Solid.EyeSlash; + public override double ScoreMultiplier => 1; + + /// + /// Slightly higher than the cutoff for . + /// + protected const float MIN_ALPHA = 0.0002f; + + protected const float TRANSITION_DURATION = 100; + + protected BindableNumber CurrentCombo; + + protected IBindable IsBreakTime; + + protected float ComboBasedAlpha; + + [SettingSource( + "Hidden at combo", + "The combo count at which the cursor becomes completely hidden", + SettingControlType = typeof(SettingsSlider) + )] + public BindableInt HiddenComboCount { get; } = new BindableInt + { + Default = 10, + Value = 10, + MinValue = 0, + MaxValue = 50, + }; + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + + public void ApplyToPlayer(Player player) + { + IsBreakTime = player.IsBreakTime.GetBoundCopy(); + } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + if (HiddenComboCount.Value == 0) return; + + CurrentCombo = scoreProcessor.Combo.GetBoundCopy(); + CurrentCombo.BindValueChanged(combo => + { + ComboBasedAlpha = Math.Max(MIN_ALPHA, 1 - (float)combo.NewValue / HiddenComboCount.Value); + }, true); + } + } + + public class HiddenComboSlider : OsuSliderBar + { + public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText; + } +} From 286754f6f73c4f0fbf87d0250ec964a088aa41bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Nov 2021 14:24:39 +0100 Subject: [PATCH 08/41] Move clipboard operation implementation down to current screen --- .../Screens/Edit/Compose/ComposeScreen.cs | 55 ++++++++++++++++++- osu.Game/Screens/Edit/Editor.cs | 51 +++-------------- osu.Game/Screens/Edit/EditorScreen.cs | 25 +++++++++ 3 files changed, 86 insertions(+), 45 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 926a2ad4e0..2214c517db 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -13,6 +13,7 @@ using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Extensions; +using osu.Game.IO.Serialization; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -21,15 +22,15 @@ namespace osu.Game.Screens.Edit.Compose { public class ComposeScreen : EditorScreenWithTimeline, IKeyBindingHandler { - [Resolved] - private IBindable beatmap { get; set; } - [Resolved] private GameHost host { get; set; } [Resolved] private EditorClock clock { get; set; } + [Resolved(Name = nameof(Editor.Clipboard))] + private Bindable clipboard { get; set; } + private HitObjectComposer composer; public ComposeScreen() @@ -104,5 +105,53 @@ namespace osu.Game.Screens.Edit.Compose } #endregion + + #region Clipboard operations + + public override void Cut() + { + base.Cut(); + + Copy(); + EditorBeatmap.RemoveRange(EditorBeatmap.SelectedHitObjects.ToArray()); + } + + public override void Copy() + { + base.Copy(); + + if (EditorBeatmap.SelectedHitObjects.Count == 0) + return; + + clipboard.Value = new ClipboardContent(EditorBeatmap).Serialize(); + } + + public override void Paste() + { + base.Paste(); + + if (string.IsNullOrEmpty(clipboard.Value)) + return; + + var objects = clipboard.Value.Deserialize().HitObjects; + + Debug.Assert(objects.Any()); + + double timeOffset = clock.CurrentTime - objects.Min(o => o.StartTime); + + foreach (var h in objects) + h.StartTime += timeOffset; + + EditorBeatmap.BeginChange(); + + EditorBeatmap.SelectedHitObjects.Clear(); + + EditorBeatmap.AddRange(objects); + EditorBeatmap.SelectedHitObjects.AddRange(objects); + + EditorBeatmap.EndChange(); + } + + #endregion } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 81b2847443..f51b527117 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework; @@ -24,7 +23,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; -using osu.Game.IO.Serialization; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; @@ -105,6 +103,9 @@ namespace osu.Game.Screens.Edit [Resolved] private MusicController music { get; set; } + [Cached(Name = nameof(Clipboard))] + public readonly Bindable Clipboard = new Bindable(); + public Editor(EditorLoader loader = null) { this.loader = loader; @@ -307,7 +308,7 @@ namespace osu.Game.Screens.Edit copyMenuItem.Action.Disabled = !hasObjects; }, true); - clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); + Clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); menuBar.Mode.ValueChanged += onModeChanged; } @@ -324,7 +325,7 @@ namespace osu.Game.Screens.Edit public void RestoreState([NotNull] EditorState state) => Schedule(() => { clock.Seek(state.Time); - clipboard.Value = state.ClipboardContent; + Clipboard.Value = state.ClipboardContent; }); protected void Save() @@ -561,45 +562,11 @@ namespace osu.Game.Screens.Edit this.Exit(); } - private readonly Bindable clipboard = new Bindable(); + protected void Cut() => currentScreen?.Cut(); - protected void Cut() - { - Copy(); - editorBeatmap.RemoveRange(editorBeatmap.SelectedHitObjects.ToArray()); - } + protected void Copy() => currentScreen?.Copy(); - protected void Copy() - { - if (editorBeatmap.SelectedHitObjects.Count == 0) - return; - - clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); - } - - protected void Paste() - { - if (string.IsNullOrEmpty(clipboard.Value)) - return; - - var objects = clipboard.Value.Deserialize().HitObjects; - - Debug.Assert(objects.Any()); - - double timeOffset = clock.CurrentTime - objects.Min(o => o.StartTime); - - foreach (var h in objects) - h.StartTime += timeOffset; - - editorBeatmap.BeginChange(); - - editorBeatmap.SelectedHitObjects.Clear(); - - editorBeatmap.AddRange(objects); - editorBeatmap.SelectedHitObjects.AddRange(objects); - - editorBeatmap.EndChange(); - } + protected void Paste() => currentScreen?.Paste(); protected void Undo() => changeHandler.RestoreState(-1); @@ -757,7 +724,7 @@ namespace osu.Game.Screens.Edit protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState { Time = clock.CurrentTimeAccurate, - ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? clipboard.Value : string.Empty + ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Value : string.Empty }); private void cancelExit() diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 2810f78835..a406aeb743 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -49,5 +49,30 @@ namespace osu.Game.Screens.Edit this.ScaleTo(0.98f, 200, Easing.OutQuint) .FadeOut(200, Easing.OutQuint); } + + #region Clipboard operations + + /// + /// Performs a "cut to clipboard" operation appropriate for the given screen. + /// + public virtual void Cut() + { + } + + /// + /// Performs a "copy to clipboard" operation appropriate for the given screen. + /// + public virtual void Copy() + { + } + + /// + /// Performs a "paste from clipboard" operation appropriate for the given screen. + /// + public virtual void Paste() + { + } + + #endregion } } From 042b05a2501a29647d2db18df15ae7e9376ac5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Nov 2021 14:54:31 +0100 Subject: [PATCH 09/41] Move clipboard action availability logic down to editor screens --- .../Screens/Edit/Compose/ComposeScreen.cs | 31 +++++++----- osu.Game/Screens/Edit/Editor.cs | 47 +++++++++++++------ osu.Game/Screens/Edit/EditorScreen.cs | 29 +++++++++++- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 2214c517db..b0a85f33d9 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -77,6 +77,13 @@ namespace osu.Game.Screens.Edit.Compose return new EditorSkinProvidingContainer(EditorBeatmap).WithChild(content); } + protected override void LoadComplete() + { + base.LoadComplete(); + EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => updateClipboardActionAvailability()); + clipboard.BindValueChanged(_ => updateClipboardActionAvailability(), true); + } + #region Input Handling public bool OnPressed(KeyBindingPressEvent e) @@ -108,30 +115,24 @@ namespace osu.Game.Screens.Edit.Compose #region Clipboard operations - public override void Cut() + protected override void PerformCut() { - base.Cut(); + base.PerformCut(); Copy(); EditorBeatmap.RemoveRange(EditorBeatmap.SelectedHitObjects.ToArray()); } - public override void Copy() + protected override void PerformCopy() { - base.Copy(); - - if (EditorBeatmap.SelectedHitObjects.Count == 0) - return; + base.PerformCopy(); clipboard.Value = new ClipboardContent(EditorBeatmap).Serialize(); } - public override void Paste() + protected override void PerformPaste() { - base.Paste(); - - if (string.IsNullOrEmpty(clipboard.Value)) - return; + base.PerformPaste(); var objects = clipboard.Value.Deserialize().HitObjects; @@ -152,6 +153,12 @@ namespace osu.Game.Screens.Edit.Compose EditorBeatmap.EndChange(); } + private void updateClipboardActionAvailability() + { + CanCut.Value = CanCopy.Value = EditorBeatmap.SelectedHitObjects.Any(); + CanPaste.Value = !string.IsNullOrEmpty(clipboard.Value); + } + #endregion } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f51b527117..b8a1dda508 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -181,10 +181,6 @@ namespace osu.Game.Screens.Edit OsuMenuItem undoMenuItem; OsuMenuItem redoMenuItem; - EditorMenuItem cutMenuItem; - EditorMenuItem copyMenuItem; - EditorMenuItem pasteMenuItem; - AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -300,19 +296,15 @@ namespace osu.Game.Screens.Edit changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); - editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => - { - bool hasObjects = editorBeatmap.SelectedHitObjects.Count > 0; - - cutMenuItem.Action.Disabled = !hasObjects; - copyMenuItem.Action.Disabled = !hasObjects; - }, true); - - Clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); - menuBar.Mode.ValueChanged += onModeChanged; } + protected override void LoadComplete() + { + base.LoadComplete(); + setUpClipboardActionAvailability(); + } + /// /// If the beatmap's track has changed, this method must be called to keep the editor in a valid state. /// @@ -562,12 +554,38 @@ namespace osu.Game.Screens.Edit this.Exit(); } + #region Clipboard support + + private EditorMenuItem cutMenuItem; + private EditorMenuItem copyMenuItem; + private EditorMenuItem pasteMenuItem; + + private readonly BindableWithCurrent canCut = new BindableWithCurrent(); + private readonly BindableWithCurrent canCopy = new BindableWithCurrent(); + private readonly BindableWithCurrent canPaste = new BindableWithCurrent(); + + private void setUpClipboardActionAvailability() + { + canCut.Current.BindValueChanged(cut => cutMenuItem.Action.Disabled = !cut.NewValue, true); + canCopy.Current.BindValueChanged(copy => copyMenuItem.Action.Disabled = !copy.NewValue, true); + canPaste.Current.BindValueChanged(paste => pasteMenuItem.Action.Disabled = !paste.NewValue, true); + } + + private void rebindClipboardBindables() + { + canCut.Current = currentScreen.CanCut; + canCopy.Current = currentScreen.CanCopy; + canPaste.Current = currentScreen.CanPaste; + } + protected void Cut() => currentScreen?.Cut(); protected void Copy() => currentScreen?.Copy(); protected void Paste() => currentScreen?.Paste(); + #endregion + protected void Undo() => changeHandler.RestoreState(-1); protected void Redo() => changeHandler.RestoreState(1); @@ -644,6 +662,7 @@ namespace osu.Game.Screens.Edit finally { updateSampleDisabledState(); + rebindClipboardBindables(); } } diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index a406aeb743..516d7a23e0 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -52,25 +53,49 @@ namespace osu.Game.Screens.Edit #region Clipboard operations + public BindableBool CanCut { get; } = new BindableBool(); + /// /// Performs a "cut to clipboard" operation appropriate for the given screen. /// - public virtual void Cut() + protected virtual void PerformCut() { } + public void Cut() + { + if (CanCut.Value) + PerformCut(); + } + + public BindableBool CanCopy { get; } = new BindableBool(); + /// /// Performs a "copy to clipboard" operation appropriate for the given screen. /// - public virtual void Copy() + protected virtual void PerformCopy() { } + public virtual void Copy() + { + if (CanCopy.Value) + PerformCopy(); + } + + public BindableBool CanPaste { get; } = new BindableBool(); + /// /// Performs a "paste from clipboard" operation appropriate for the given screen. /// + protected virtual void PerformPaste() + { + } + public virtual void Paste() { + if (CanPaste.Value) + PerformPaste(); } #endregion From 410e9159d1e70a132a1a404a773a370d8ee050c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Nov 2021 15:27:11 +0100 Subject: [PATCH 10/41] Fix test failures due to missing dependencies --- .../Editor/TestSceneManiaComposeScreen.cs | 4 ++++ osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 0f520215a1..60a5051fb5 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -22,6 +23,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Resolved] private SkinManager skins { get; set; } + [Cached(Name = nameof(Screens.Edit.Editor.Clipboard))] + private Bindable clipboard = new Bindable(); + [SetUpSteps] public void SetUpSteps() { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 4813598c9d..8796088ec6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; @@ -26,6 +27,9 @@ namespace osu.Game.Tests.Visual.Editing } }); + [Cached(Name = nameof(Editor.Clipboard))] + private Bindable clipboard = new Bindable(); + [BackgroundDependencyLoader] private void load() { From fbfed167565b3a8646ad6915d9d04f71f7eae42d Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Tue, 9 Nov 2021 23:05:25 +0100 Subject: [PATCH 11/41] Started on implementing a spinner gap check for catch --- .idea/.idea.osu.Desktop/.idea/indexLayout.xml | 5 + osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 + .../Edit/CatchBeatmapVerifier.cs | 24 +++++ .../Edit/Checks/CheckTooShortSpinnerGap.cs | 91 +++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs diff --git a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml index 7b08163ceb..90e8a163b9 100644 --- a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml +++ b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml @@ -1,5 +1,10 @@ + + + + + diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9fee6b2bc1..6b26a915dd 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -188,5 +188,7 @@ namespace osu.Game.Rulesets.Catch public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); + + public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier(); } } diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs new file mode 100644 index 0000000000..6aa1749ff9 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs @@ -0,0 +1,24 @@ +// 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.Rulesets.Catch.Edit.Checks; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class CatchBeatmapVerifier : IBeatmapVerifier + { + private readonly List checks = new List + { + new CheckTooShortSpinnerGap() + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + return checks.SelectMany(check => check.Run(context)); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs b/osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs new file mode 100644 index 0000000000..7b0b5c74ce --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs @@ -0,0 +1,91 @@ +// 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 osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Catch.Edit.Checks +{ + public class CheckTooShortSpinnerGap : ICheck + { + private static readonly int[] spinner_start_delta_threshold = { 250, 250, 125, 125, 62, 62 }; + private static readonly int[] spinner_end_delta_threshold = { 250, 250, 250, 125, 125, 125 }; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Too short spinner gap"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateSpinnerStartGap(this), + new IssueTemplateSpinnerEndGap(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + var hitObjects = context.Beatmap.HitObjects; + int interpretedDifficulty = (int)context.InterpretedDifficulty; + int expectedStartDelta = spinner_start_delta_threshold[interpretedDifficulty]; + int expectedEndDelta = spinner_end_delta_threshold[interpretedDifficulty]; + + for (int i = 0; i < hitObjects.Count - 1; ++i) + { + if (!(hitObjects[i] is BananaShower bananaShower)) + continue; + + if (i != 0 && hitObjects[i - 1] is CatchHitObject previousHitObject && !(previousHitObject is BananaShower)) + { + double spinnerStartDelta = bananaShower.StartTime - previousHitObject.GetEndTime(); + + if (spinnerStartDelta < expectedStartDelta) + { + yield return new IssueTemplateSpinnerStartGap(this) + .Create(spinnerStartDelta, expectedStartDelta, bananaShower, previousHitObject); + } + } + + if (hitObjects[i + 1] is CatchHitObject nextHitObject && !(nextHitObject is BananaShower)) + { + double spinnerEndDelta = nextHitObject.StartTime - bananaShower.EndTime; + + if (spinnerEndDelta < expectedEndDelta) + { + yield return new IssueTemplateSpinnerEndGap(this) + .Create(spinnerEndDelta, expectedEndDelta, bananaShower, nextHitObject); + } + } + } + } + + public abstract class IssueTemplateSpinnerGap : IssueTemplate + { + protected IssueTemplateSpinnerGap(ICheck check, IssueType issueType, string unformattedMessage) + : base(check, issueType, unformattedMessage) + { + } + + public Issue Create(double deltaTime, int expectedDeltaTime, params HitObject[] hitObjects) + { + return new Issue(hitObjects, this, Math.Floor(deltaTime), expectedDeltaTime); + } + } + + public class IssueTemplateSpinnerStartGap : IssueTemplateSpinnerGap + { + public IssueTemplateSpinnerStartGap(ICheck check) + : base(check, IssueType.Problem, "There is only {0} ms apart between the start of the spinner and the last object, there should be {1} ms or more.") + { + } + } + + public class IssueTemplateSpinnerEndGap : IssueTemplateSpinnerGap + { + public IssueTemplateSpinnerEndGap(ICheck check) + : base(check, IssueType.Problem, "There is only {0} ms apart between the end of the spinner and the next object, there should be {1} ms or more.") + { + } + } + } +} From 5d8f35f3c973a941a017642cf09ba3f167ab7bd2 Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Wed, 10 Nov 2021 00:16:29 +0100 Subject: [PATCH 12/41] Code cleanup and added tests for the spinner check --- .../Editor/Checks/TestCheckBananaShowerGap.cs | 118 ++++++++++++++++++ .../Edit/CatchBeatmapVerifier.cs | 2 +- ...tSpinnerGap.cs => CheckBananaShowerGap.cs} | 31 +++-- 3 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/Checks/TestCheckBananaShowerGap.cs rename osu.Game.Rulesets.Catch/Edit/Checks/{CheckTooShortSpinnerGap.cs => CheckBananaShowerGap.cs} (71%) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/Checks/TestCheckBananaShowerGap.cs b/osu.Game.Rulesets.Catch.Tests/Editor/Checks/TestCheckBananaShowerGap.cs new file mode 100644 index 0000000000..055c8429d7 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/Checks/TestCheckBananaShowerGap.cs @@ -0,0 +1,118 @@ +// 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.Beatmaps; +using osu.Game.Rulesets.Catch.Edit.Checks; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Catch.Tests.Editor.Checks +{ + [TestFixture] + public class TestCheckBananaShowerGap + { + private CheckBananaShowerGap check; + + [SetUp] + public void Setup() + { + check = new CheckBananaShowerGap(); + } + + [Test] + public void TestAllowedSpinnerGaps() + { + assertOk(mockBeatmap(250, 1000, 1250), DifficultyRating.Easy); + assertOk(mockBeatmap(250, 1000, 1250), DifficultyRating.Normal); + assertOk(mockBeatmap(125, 1000, 1250), DifficultyRating.Hard); + assertOk(mockBeatmap(125, 1000, 1125), DifficultyRating.Insane); + assertOk(mockBeatmap(62, 1000, 1125), DifficultyRating.Expert); + assertOk(mockBeatmap(62, 1000, 1125), DifficultyRating.ExpertPlus); + } + + [Test] + public void TestDisallowedSpinnerGapStart() + { + assertTooShortSpinnerStart(mockBeatmap(249, 1000, 1250), DifficultyRating.Easy); + assertTooShortSpinnerStart(mockBeatmap(249, 1000, 1250), DifficultyRating.Normal); + assertTooShortSpinnerStart(mockBeatmap(124, 1000, 1250), DifficultyRating.Hard); + assertTooShortSpinnerStart(mockBeatmap(124, 1000, 1250), DifficultyRating.Insane); + assertTooShortSpinnerStart(mockBeatmap(61, 1000, 1250), DifficultyRating.Expert); + assertTooShortSpinnerStart(mockBeatmap(61, 1000, 1250), DifficultyRating.ExpertPlus); + } + + [Test] + public void TestDisallowedSpinnerGapEnd() + { + assertTooShortSpinnerEnd(mockBeatmap(250, 1000, 1249), DifficultyRating.Easy); + assertTooShortSpinnerEnd(mockBeatmap(250, 1000, 1249), DifficultyRating.Normal); + assertTooShortSpinnerEnd(mockBeatmap(125, 1000, 1249), DifficultyRating.Hard); + assertTooShortSpinnerEnd(mockBeatmap(125, 1000, 1124), DifficultyRating.Insane); + assertTooShortSpinnerEnd(mockBeatmap(62, 1000, 1124), DifficultyRating.Expert); + assertTooShortSpinnerEnd(mockBeatmap(62, 1000, 1124), DifficultyRating.ExpertPlus); + } + + [Test] + public void TestConsecutiveSpinners() + { + var spinnerConsecutiveBeatmap = new Beatmap + { + HitObjects = new List + { + new BananaShower { StartTime = 0, EndTime = 100, X = 0 }, + new BananaShower { StartTime = 101, EndTime = 200, X = 0 }, + new BananaShower { StartTime = 201, EndTime = 300, X = 0 } + } + }; + + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Easy); + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Normal); + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Hard); + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Insane); + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Expert); + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.ExpertPlus); + } + + private Beatmap mockBeatmap(double bananaShowerStart, double bananaShowerEnd, double nextFruitStart) + { + return new Beatmap + { + HitObjects = new List + { + new Fruit { StartTime = 0, X = 0 }, + new BananaShower { StartTime = bananaShowerStart, EndTime = bananaShowerEnd, X = 0 }, + new Fruit { StartTime = nextFruitStart, X = 0 } + } + }; + } + + private void assertOk(IBeatmap beatmap, DifficultyRating difficultyRating) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); + Assert.That(check.Run(context), Is.Empty); + } + + private void assertTooShortSpinnerStart(IBeatmap beatmap, DifficultyRating difficultyRating) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.All(issue => issue.Template is CheckBananaShowerGap.IssueTemplateBananaShowerStartGap)); + } + + private void assertTooShortSpinnerEnd(IBeatmap beatmap, DifficultyRating difficultyRating) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.All(issue => issue.Template is CheckBananaShowerGap.IssueTemplateBananaShowerEndGap)); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs index 6aa1749ff9..c7a41a4e22 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Edit { private readonly List checks = new List { - new CheckTooShortSpinnerGap() + new CheckBananaShowerGap() }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs similarity index 71% rename from osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs rename to osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs index 7b0b5c74ce..909a3110b1 100644 --- a/osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs +++ b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs @@ -10,7 +10,10 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Catch.Edit.Checks { - public class CheckTooShortSpinnerGap : ICheck + /// + /// Check the spinner/banana shower gaps specified in the osu!catch difficulty specific ranking criteria. + /// + public class CheckBananaShowerGap : ICheck { private static readonly int[] spinner_start_delta_threshold = { 250, 250, 125, 125, 62, 62 }; private static readonly int[] spinner_end_delta_threshold = { 250, 250, 250, 125, 125, 125 }; @@ -19,8 +22,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks public IEnumerable PossibleTemplates => new IssueTemplate[] { - new IssueTemplateSpinnerStartGap(this), - new IssueTemplateSpinnerEndGap(this) + new IssueTemplateBananaShowerStartGap(this), + new IssueTemplateBananaShowerEndGap(this) }; public IEnumerable Run(BeatmapVerifierContext context) @@ -35,33 +38,35 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks if (!(hitObjects[i] is BananaShower bananaShower)) continue; + // Skip if the previous hitobject is a banana shower, consecutive spinners are allowed if (i != 0 && hitObjects[i - 1] is CatchHitObject previousHitObject && !(previousHitObject is BananaShower)) { double spinnerStartDelta = bananaShower.StartTime - previousHitObject.GetEndTime(); if (spinnerStartDelta < expectedStartDelta) { - yield return new IssueTemplateSpinnerStartGap(this) + yield return new IssueTemplateBananaShowerStartGap(this) .Create(spinnerStartDelta, expectedStartDelta, bananaShower, previousHitObject); } } + // Skip if the next hitobject is a banana shower, consecutive spinners are allowed if (hitObjects[i + 1] is CatchHitObject nextHitObject && !(nextHitObject is BananaShower)) { double spinnerEndDelta = nextHitObject.StartTime - bananaShower.EndTime; if (spinnerEndDelta < expectedEndDelta) { - yield return new IssueTemplateSpinnerEndGap(this) + yield return new IssueTemplateBananaShowerEndGap(this) .Create(spinnerEndDelta, expectedEndDelta, bananaShower, nextHitObject); } } } } - public abstract class IssueTemplateSpinnerGap : IssueTemplate + public abstract class IssueTemplateBananaShowerGap : IssueTemplate { - protected IssueTemplateSpinnerGap(ICheck check, IssueType issueType, string unformattedMessage) + protected IssueTemplateBananaShowerGap(ICheck check, IssueType issueType, string unformattedMessage) : base(check, issueType, unformattedMessage) { } @@ -72,18 +77,18 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks } } - public class IssueTemplateSpinnerStartGap : IssueTemplateSpinnerGap + public class IssueTemplateBananaShowerStartGap : IssueTemplateBananaShowerGap { - public IssueTemplateSpinnerStartGap(ICheck check) - : base(check, IssueType.Problem, "There is only {0} ms apart between the start of the spinner and the last object, there should be {1} ms or more.") + public IssueTemplateBananaShowerStartGap(ICheck check) + : base(check, IssueType.Problem, "There is only {0} ms apart between the start of the spinner and the last object, it should not be less than {1} ms.") { } } - public class IssueTemplateSpinnerEndGap : IssueTemplateSpinnerGap + public class IssueTemplateBananaShowerEndGap : IssueTemplateBananaShowerGap { - public IssueTemplateSpinnerEndGap(ICheck check) - : base(check, IssueType.Problem, "There is only {0} ms apart between the end of the spinner and the next object, there should be {1} ms or more.") + public IssueTemplateBananaShowerEndGap(ICheck check) + : base(check, IssueType.Problem, "There is only {0} ms apart between the end of the spinner and the next object, it should not be less than {1} ms.") { } } From bd5caceeb1f2d95d40339fe08af3720d20749967 Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Wed, 10 Nov 2021 00:23:14 +0100 Subject: [PATCH 13/41] Fixed typo in banana shower gap check message --- osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs index 909a3110b1..d6671cb9db 100644 --- a/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs +++ b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks public class IssueTemplateBananaShowerStartGap : IssueTemplateBananaShowerGap { public IssueTemplateBananaShowerStartGap(ICheck check) - : base(check, IssueType.Problem, "There is only {0} ms apart between the start of the spinner and the last object, it should not be less than {1} ms.") + : base(check, IssueType.Problem, "There is only {0} ms between the start of the spinner and the last object, it should not be less than {1} ms.") { } } @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks public class IssueTemplateBananaShowerEndGap : IssueTemplateBananaShowerGap { public IssueTemplateBananaShowerEndGap(ICheck check) - : base(check, IssueType.Problem, "There is only {0} ms apart between the end of the spinner and the next object, it should not be less than {1} ms.") + : base(check, IssueType.Problem, "There is only {0} ms between the end of the spinner and the next object, it should not be less than {1} ms.") { } } From 4e092fed0f472a0755fe650b4f05ac18e3dae65e Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Wed, 10 Nov 2021 00:35:09 +0100 Subject: [PATCH 14/41] Removed accidentally added Rider legacy UserContentModel --- .idea/.idea.osu.Desktop/.idea/indexLayout.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml index 90e8a163b9..7b08163ceb 100644 --- a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml +++ b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml @@ -1,10 +1,5 @@ - - - - - From ce0eb0b26ff5a3ab152f5e9e48f4f991293b990e Mon Sep 17 00:00:00 2001 From: Semyon Rozhkov Date: Wed, 10 Nov 2021 03:53:30 +0300 Subject: [PATCH 15/41] Using IUpdatableByPlayfield --- osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index 7b55150582..b0e374898e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -13,7 +13,7 @@ using osu.Game.Utils; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModNoScope : ModNoScope, IApplicableToBeatmap, IApplicableToDrawableRuleset + public class CatchModNoScope : ModNoScope, IApplicableToBeatmap, IUpdatableByPlayfield { public override string Description => "Where's the catcher?"; @@ -24,15 +24,12 @@ namespace osu.Game.Rulesets.Catch.Mods BananaShowerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); } - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + public virtual void Update(Playfield playfield) { - var playfield = (CatchPlayfield)drawableRuleset.Playfield; - playfield.OnUpdate += _ => - { - bool shouldAlwaysShowCatcher = IsBreakTime.Value || BananaShowerPeriods.IsInAny(playfield.Clock.CurrentTime); - float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha; - playfield.CatcherArea.Alpha = (float)Interpolation.Lerp(playfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); - }; + var catchPlayfield = (CatchPlayfield)playfield; + bool shouldAlwaysShowCatcher = IsBreakTime.Value || BananaShowerPeriods.IsInAny(catchPlayfield.Clock.CurrentTime); + float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha; + catchPlayfield.CatcherArea.Alpha = (float)Interpolation.Lerp(catchPlayfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(catchPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } } } From 41f4f0ab5e7eb373e8abe6d25c627c651415fe21 Mon Sep 17 00:00:00 2001 From: Semyon Rozhkov Date: Wed, 10 Nov 2021 03:57:22 +0300 Subject: [PATCH 16/41] Different setting slider description in each mod --- osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs | 16 ++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 16 ++++++++++++++++ osu.Game/Rulesets/Mods/ModNoScope.cs | 15 +-------------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index b0e374898e..bec53fc3bd 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -3,9 +3,12 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Framework.Utils; +using osu.Game.Configuration; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.UI; @@ -19,6 +22,19 @@ namespace osu.Game.Rulesets.Catch.Mods public PeriodTracker BananaShowerPeriods; + [SettingSource( + "Hidden at combo", + "The combo count at which the catcher becomes completely hidden", + SettingControlType = typeof(SettingsSlider) + )] + public override BindableInt HiddenComboCount { get; } = new BindableInt + { + Default = 10, + Value = 10, + MinValue = 0, + MaxValue = 50, + }; + public void ApplyToBeatmap(IBeatmap beatmap) { BananaShowerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index 3f9cd098cd..a73ddde110 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -3,8 +3,11 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; @@ -18,6 +21,19 @@ namespace osu.Game.Rulesets.Osu.Mods private PeriodTracker spinnerPeriods; + [SettingSource( + "Hidden at combo", + "The combo count at which the cursor becomes completely hidden", + SettingControlType = typeof(SettingsSlider) + )] + public override BindableInt HiddenComboCount { get; } = new BindableInt + { + Default = 10, + Value = 10, + MinValue = 0, + MaxValue = 50, + }; + public void ApplyToBeatmap(IBeatmap beatmap) { spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index 9e2734b775..7a935eb38f 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -6,9 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -36,18 +34,7 @@ namespace osu.Game.Rulesets.Mods protected float ComboBasedAlpha; - [SettingSource( - "Hidden at combo", - "The combo count at which the cursor becomes completely hidden", - SettingControlType = typeof(SettingsSlider) - )] - public BindableInt HiddenComboCount { get; } = new BindableInt - { - Default = 10, - Value = 10, - MinValue = 0, - MaxValue = 50, - }; + public abstract BindableInt HiddenComboCount { get; } public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; From 577bdade5b6369952a65fd44d8c3ad8663da92cd Mon Sep 17 00:00:00 2001 From: Semyon Rozhkov Date: Wed, 10 Nov 2021 04:10:05 +0300 Subject: [PATCH 17/41] Hide catcher during banana shower --- .../Mods/TestSceneCatchModNoScope.cs | 46 ------------------- .../Mods/CatchModNoScope.cs | 15 +----- 2 files changed, 2 insertions(+), 59 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index 5d8bbad384..13d78145b3 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -2,15 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Utils; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Tests.Visual; @@ -61,47 +58,6 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); } - [Test] - public void TestVisibleDuringBananaShower() - { - CreateModTest(new ModTestData - { - Mod = new CatchModNoScope - { - HiddenComboCount = { Value = 0 }, - }, - Autoplay = true, - PassCondition = () => true, - Beatmap = new Beatmap - { - HitObjects = new List - { - new Fruit - { - X = CatchPlayfield.CENTER_X, - StartTime = 1000, - }, - new BananaShower - { - StartTime = 2000, - Duration = 2000, - }, - new Fruit - { - X = CatchPlayfield.CENTER_X, - StartTime = 5000, - } - } - } - }); - - AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); - AddUntilStep("wait for start of banana shower", isBananaShower); - AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); - AddUntilStep("wait for end of banana shower", () => !isBananaShower()); - AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); - } - [Test] public void TestVisibleAfterComboBreak() { @@ -143,8 +99,6 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); } - private bool isBananaShower() => Player.ChildrenOfType().SingleOrDefault() != null; - private bool isBreak() => Player.IsBreakTime.Value; private bool catcherAlphaAlmostEquals(float alpha) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index bec53fc3bd..97c6d46e66 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -2,26 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.UI; -using osu.Game.Utils; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModNoScope : ModNoScope, IApplicableToBeatmap, IUpdatableByPlayfield + public class CatchModNoScope : ModNoScope, IUpdatableByPlayfield { public override string Description => "Where's the catcher?"; - public PeriodTracker BananaShowerPeriods; - [SettingSource( "Hidden at combo", "The combo count at which the catcher becomes completely hidden", @@ -35,15 +29,10 @@ namespace osu.Game.Rulesets.Catch.Mods MaxValue = 50, }; - public void ApplyToBeatmap(IBeatmap beatmap) - { - BananaShowerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); - } - public virtual void Update(Playfield playfield) { var catchPlayfield = (CatchPlayfield)playfield; - bool shouldAlwaysShowCatcher = IsBreakTime.Value || BananaShowerPeriods.IsInAny(catchPlayfield.Clock.CurrentTime); + bool shouldAlwaysShowCatcher = IsBreakTime.Value; float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha; catchPlayfield.CatcherArea.Alpha = (float)Interpolation.Lerp(catchPlayfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(catchPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } From 5dc6a9ed21ac66ae681c5878dc9f66990fc9014e Mon Sep 17 00:00:00 2001 From: Naxesss <30292137+Naxesss@users.noreply.github.com> Date: Wed, 10 Nov 2021 05:04:30 +0100 Subject: [PATCH 18/41] Add background stream closed test --- .../Checks/CheckBackgroundQualityTest.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 2918dde2db..42c1121c74 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -111,6 +111,24 @@ namespace osu.Game.Tests.Editing.Checks Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed); } + [Test] + public void TestStreamClosed() + { + var background = new Texture(1920, 1080); + var stream = new Mock(new byte[1024 * 1024]); + + var mock = new Mock(); + mock.SetupGet(w => w.Beatmap).Returns(beatmap); + mock.SetupGet(w => w.Background).Returns(background); + mock.Setup(w => w.GetStream(It.IsAny())).Returns(stream.Object); + + var context = new BeatmapVerifierContext(beatmap, mock.Object); + + Assert.That(check.Run(context), Is.Empty); + + stream.Verify(x => x.Close(), Times.Once()); + } + private BeatmapVerifierContext getContext(Texture background, [CanBeNull] byte[] fileBytes = null) { return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, fileBytes).Object); From b88818579992ce0751aa51b7f2fa93bf369e49f2 Mon Sep 17 00:00:00 2001 From: Naxesss <30292137+Naxesss@users.noreply.github.com> Date: Wed, 10 Nov 2021 05:06:11 +0100 Subject: [PATCH 19/41] Properly dispose of `Stream` in bg quality check --- .../Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 8fa79e2ee8..7ce2ee802e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.IO; using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit.Checks @@ -48,10 +49,14 @@ namespace osu.Game.Rulesets.Edit.Checks yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); - double filesizeMb = context.WorkingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); - if (filesizeMb > max_filesize_mb) - yield return new IssueTemplateTooUncompressed(this).Create(filesizeMb); + using (Stream stream = context.WorkingBeatmap.GetStream(storagePath)) + { + double filesizeMb = stream.Length / (1024d * 1024d); + + if (filesizeMb > max_filesize_mb) + yield return new IssueTemplateTooUncompressed(this).Create(filesizeMb); + } } public class IssueTemplateTooHighResolution : IssueTemplate From 52c740b3770480eda15fc006497a565c691d2308 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Nov 2021 13:44:24 +0900 Subject: [PATCH 20/41] Add failing test showing team display display failure --- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 4 ++++ .../Multiplayer/Participants/TeamDisplay.cs | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index ad92886bab..1efa8d6810 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -127,9 +127,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead); + AddUntilStep("team displays are not displaying teams", () => multiplayerScreenStack.ChildrenOfType().All(d => d.DisplayedTeam == null)); + AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus)); AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); + + AddUntilStep("team displays are displaying teams", () => multiplayerScreenStack.ChildrenOfType().All(d => d.DisplayedTeam != null)); } private void createRoom(Func room) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index 833fbd6605..f2d09d44e9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -88,7 +88,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants }); } - private int? displayedTeam; + public int? DisplayedTeam { get; private set; } protected override void OnRoomUpdated() { @@ -102,19 +102,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants int? newTeam = (userRoomState as TeamVersusUserState)?.TeamID; - if (newTeam == displayedTeam) + if (newTeam == DisplayedTeam) return; // only play the sample if an already valid team changes to another valid team. // this avoids playing a sound for each user if the match type is changed to/from a team mode. - if (newTeam != null && displayedTeam != null) + if (newTeam != null && DisplayedTeam != null) sampleTeamSwap?.Play(); - displayedTeam = newTeam; + DisplayedTeam = newTeam; - if (displayedTeam != null) + if (DisplayedTeam != null) { - box.FadeColour(getColourForTeam(displayedTeam.Value), duration, Easing.OutQuint); + box.FadeColour(getColourForTeam(DisplayedTeam.Value), duration, Easing.OutQuint); box.ScaleTo(new Vector2(box.Scale.X < 0 ? 1 : -1, 1), duration, Easing.OutQuint); this.ScaleTo(Vector2.One, duration, Easing.OutQuint); From 328c72c3589b2ce21bcbccf2a8b0a8b4760634bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Nov 2021 13:38:02 +0900 Subject: [PATCH 21/41] Fix `TeamDisplay` not showing after changing to team versus --- .../Multiplayer/Participants/TeamDisplay.cs | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index f2d09d44e9..1bf62241f2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -29,52 +29,51 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [Resolved] private OsuColour colours { get; set; } + private OsuClickableContainer clickableContent; + public TeamDisplay(MultiplayerRoomUser user) { this.user = user; RelativeSizeAxes = Axes.Y; - Width = 15; + + AutoSizeAxes = Axes.X; Margin = new MarginPadding { Horizontal = 3 }; - - Alpha = 0; - Scale = new Vector2(0, 1); } [BackgroundDependencyLoader] private void load(AudioManager audio) { - box = new Container + InternalChild = clickableContent = new OsuClickableContainer { - RelativeSizeAxes = Axes.Both, - CornerRadius = 5, - Masking = true, + Width = 15, + Alpha = 0, Scale = new Vector2(0, 1), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = new Box + RelativeSizeAxes = Axes.Y, + Action = changeTeam, + Child = box = new Container { - Colour = Color4.White, RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Masking = true, + Scale = new Vector2(0, 1), Anchor = Anchor.Centre, Origin = Anchor.Centre, + Child = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } } }; if (Client.LocalUser?.Equals(user) == true) { - InternalChild = new OsuClickableContainer - { - RelativeSizeAxes = Axes.Both, - TooltipText = "Change team", - Action = changeTeam, - Child = box - }; - } - else - { - InternalChild = box; + clickableContent.Action = changeTeam; + clickableContent.TooltipText = "Change team"; } sampleTeamSwap = audio.Samples.Get(@"Multiplayer/team-swap"); @@ -117,13 +116,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants box.FadeColour(getColourForTeam(DisplayedTeam.Value), duration, Easing.OutQuint); box.ScaleTo(new Vector2(box.Scale.X < 0 ? 1 : -1, 1), duration, Easing.OutQuint); - this.ScaleTo(Vector2.One, duration, Easing.OutQuint); - this.FadeIn(duration); + clickableContent.ScaleTo(Vector2.One, duration, Easing.OutQuint); + clickableContent.FadeIn(duration); } else { - this.ScaleTo(new Vector2(0, 1), duration, Easing.OutQuint); - this.FadeOut(duration); + clickableContent.ScaleTo(new Vector2(0, 1), duration, Easing.OutQuint); + clickableContent.FadeOut(duration); } } From 72ee2b155641d16a428083302bff6da667d9e6c4 Mon Sep 17 00:00:00 2001 From: Naxesss <30292137+Naxesss@users.noreply.github.com> Date: Wed, 10 Nov 2021 06:18:40 +0100 Subject: [PATCH 22/41] Refactor to avoid duplicate code --- .../Checks/CheckBackgroundQualityTest.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 42c1121c74..05bfae7e63 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Editing.Checks { // While this is a problem, it is out of scope for this check and is caught by a different one. beatmap.Metadata.BackgroundFile = string.Empty; - var context = getContext(null, System.Array.Empty()); + var context = getContext(null, new MemoryStream(System.Array.Empty())); Assert.That(check.Run(context), Is.Empty); } @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestTooUncompressed() { - var context = getContext(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); + var context = getContext(new Texture(1920, 1080), new MemoryStream(new byte[1024 * 1024 * 3])); var issues = check.Run(context).ToList(); @@ -117,31 +117,26 @@ namespace osu.Game.Tests.Editing.Checks var background = new Texture(1920, 1080); var stream = new Mock(new byte[1024 * 1024]); - var mock = new Mock(); - mock.SetupGet(w => w.Beatmap).Returns(beatmap); - mock.SetupGet(w => w.Background).Returns(background); - mock.Setup(w => w.GetStream(It.IsAny())).Returns(stream.Object); - - var context = new BeatmapVerifierContext(beatmap, mock.Object); + var context = getContext(background, stream.Object); Assert.That(check.Run(context), Is.Empty); stream.Verify(x => x.Close(), Times.Once()); } - private BeatmapVerifierContext getContext(Texture background, [CanBeNull] byte[] fileBytes = null) + private BeatmapVerifierContext getContext(Texture background, [CanBeNull] Stream stream = null) { - return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, fileBytes).Object); + return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, stream).Object); } /// - /// Returns the mock of the working beatmap with the given background and filesize. + /// Returns the mock of the working beatmap with the given background and its file stream. /// /// The texture of the background. - /// The bytes that represent the background file. - private Mock getMockWorkingBeatmap(Texture background, [CanBeNull] byte[] fileBytes = null) + /// The stream representing the background file. + private Mock getMockWorkingBeatmap(Texture background, [CanBeNull] Stream stream = null) { - var stream = new MemoryStream(fileBytes ?? new byte[1024 * 1024]); + stream ??= new MemoryStream(new byte[1024 * 1024]); var mock = new Mock(); mock.SetupGet(w => w.Beatmap).Returns(beatmap); From f4ef8419729f4c80ccd8b1a666c180b2267a73f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Nov 2021 18:27:23 +0900 Subject: [PATCH 23/41] Add fallback for cases where beatmap has no author/title/artist specified --- osu.Game.Tests/Models/DisplayStringTest.cs | 22 +++++++++++++++++++ .../Beatmaps/BeatmapMetadataInfoExtensions.cs | 8 +++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Models/DisplayStringTest.cs b/osu.Game.Tests/Models/DisplayStringTest.cs index 9a3c32bfb2..754a849ac8 100644 --- a/osu.Game.Tests/Models/DisplayStringTest.cs +++ b/osu.Game.Tests/Models/DisplayStringTest.cs @@ -17,6 +17,8 @@ namespace osu.Game.Tests.Models { private static readonly object[][] test_cases = { + new object[] { makeNoMetadataMockBeatmapSet(), "unknown artist - unknown title" }, + new object[] { makeNoAuthorMockBeatmapSet(), "artist - title" }, new object[] { makeMockBeatmapSet(), "artist - title (author)" }, new object[] { makeMockBeatmap(), "artist - title (author) [difficulty]" }, new object[] { makeMockMetadata(), "artist - title (author)" }, @@ -29,6 +31,26 @@ namespace osu.Game.Tests.Models [TestCaseSource(nameof(test_cases))] public void TestDisplayString(object model, string expected) => Assert.That(model.GetDisplayString(), Is.EqualTo(expected)); + private static IBeatmapSetInfo makeNoAuthorMockBeatmapSet() + { + var mock = new Mock(); + + mock.Setup(m => m.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Metadata.Title).Returns("title"); + mock.Setup(m => m.Metadata.Author.Username).Returns(string.Empty); + + return mock.Object; + } + + private static IBeatmapSetInfo makeNoMetadataMockBeatmapSet() + { + var mock = new Mock(); + + mock.Setup(m => m.Metadata).Returns(new BeatmapMetadata()); + + return mock.Object; + } + private static IBeatmapSetInfo makeMockBeatmapSet() { var mock = new Mock(); diff --git a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs index 32fb389e9a..7aab6a7a9b 100644 --- a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs @@ -27,8 +27,12 @@ namespace osu.Game.Beatmaps /// public static string GetDisplayTitle(this IBeatmapMetadataInfo metadataInfo) { - string author = string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $"({metadataInfo.Author.Username})"; - return $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim(); + string author = string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $" ({metadataInfo.Author.Username})"; + + string artist = string.IsNullOrEmpty(metadataInfo.Artist) ? "unknown artist" : metadataInfo.Artist; + string title = string.IsNullOrEmpty(metadataInfo.Title) ? "unknown title" : metadataInfo.Title; + + return $"{artist} - {title}{author}".Trim(); } /// From a52d9363f9a3a7dc8fe6e9b6d9e83a97f20985e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 11:09:09 +0100 Subject: [PATCH 24/41] Rewrite tests to be easier to follow --- osu.Game.Tests/Models/DisplayStringTest.cs | 83 ++++++++++++---------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Models/DisplayStringTest.cs b/osu.Game.Tests/Models/DisplayStringTest.cs index 754a849ac8..cac5dd1aaa 100644 --- a/osu.Game.Tests/Models/DisplayStringTest.cs +++ b/osu.Game.Tests/Models/DisplayStringTest.cs @@ -15,23 +15,20 @@ namespace osu.Game.Tests.Models [TestFixture] public class DisplayStringTest { - private static readonly object[][] test_cases = + [Test] + public void TestBeatmapSet() { - new object[] { makeNoMetadataMockBeatmapSet(), "unknown artist - unknown title" }, - new object[] { makeNoAuthorMockBeatmapSet(), "artist - title" }, - new object[] { makeMockBeatmapSet(), "artist - title (author)" }, - new object[] { makeMockBeatmap(), "artist - title (author) [difficulty]" }, - new object[] { makeMockMetadata(), "artist - title (author)" }, - new object[] { makeMockScore(), "user playing artist - title (author) [difficulty]" }, - new object[] { makeMockRuleset(), "ruleset" }, - new object[] { makeMockUser(), "user" }, - new object[] { new Fallback(), "fallback" } - }; + var mock = new Mock(); - [TestCaseSource(nameof(test_cases))] - public void TestDisplayString(object model, string expected) => Assert.That(model.GetDisplayString(), Is.EqualTo(expected)); + mock.Setup(m => m.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Metadata.Title).Returns("title"); + mock.Setup(m => m.Metadata.Author.Username).Returns("author"); - private static IBeatmapSetInfo makeNoAuthorMockBeatmapSet() + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)")); + } + + [Test] + public void TestBeatmapSetWithNoAuthor() { var mock = new Mock(); @@ -39,38 +36,34 @@ namespace osu.Game.Tests.Models mock.Setup(m => m.Metadata.Title).Returns("title"); mock.Setup(m => m.Metadata.Author.Username).Returns(string.Empty); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title")); } - private static IBeatmapSetInfo makeNoMetadataMockBeatmapSet() + [Test] + public void TestBeatmapSetWithNoMetadata() { var mock = new Mock(); mock.Setup(m => m.Metadata).Returns(new BeatmapMetadata()); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("unknown artist - unknown title")); } - private static IBeatmapSetInfo makeMockBeatmapSet() - { - var mock = new Mock(); - - mock.Setup(m => m.Metadata).Returns(makeMockMetadata); - - return mock.Object; - } - - private static IBeatmapInfo makeMockBeatmap() + [Test] + public void TestBeatmap() { var mock = new Mock(); - mock.Setup(m => m.Metadata).Returns(makeMockMetadata); + mock.Setup(m => m.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Metadata.Title).Returns("title"); + mock.Setup(m => m.Metadata.Author.Username).Returns("author"); mock.Setup(m => m.DifficultyName).Returns("difficulty"); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author) [difficulty]")); } - private static IBeatmapMetadataInfo makeMockMetadata() + [Test] + public void TestMetadata() { var mock = new Mock(); @@ -78,35 +71,49 @@ namespace osu.Game.Tests.Models mock.Setup(m => m.Title).Returns("title"); mock.Setup(m => m.Author.Username).Returns("author"); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)")); } - private static IScoreInfo makeMockScore() + [Test] + public void TestScore() { var mock = new Mock(); mock.Setup(m => m.User).Returns(new APIUser { Username = "user" }); // TODO: temporary. - mock.Setup(m => m.Beatmap).Returns(makeMockBeatmap); + mock.Setup(m => m.Beatmap.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Beatmap.Metadata.Title).Returns("title"); + mock.Setup(m => m.Beatmap.Metadata.Author.Username).Returns("author"); + mock.Setup(m => m.Beatmap.DifficultyName).Returns("difficulty"); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("user playing artist - title (author) [difficulty]")); } - private static IRulesetInfo makeMockRuleset() + [Test] + public void TestRuleset() { var mock = new Mock(); mock.Setup(m => m.Name).Returns("ruleset"); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("ruleset")); } - private static IUser makeMockUser() + [Test] + public void TestUser() { var mock = new Mock(); mock.Setup(m => m.Username).Returns("user"); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("user")); + } + + [Test] + public void TestFallback() + { + var fallback = new Fallback(); + + Assert.That(fallback.GetDisplayString(), Is.EqualTo("fallback")); } private class Fallback From 6d04823b05e67470b6b62ee6b2eb63997c1c8334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 12:00:36 +0100 Subject: [PATCH 25/41] Remove unnecessary `virtual` specs --- osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index 97c6d46e66..a24a6227fe 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Mods MaxValue = 50, }; - public virtual void Update(Playfield playfield) + public void Update(Playfield playfield) { var catchPlayfield = (CatchPlayfield)playfield; bool shouldAlwaysShowCatcher = IsBreakTime.Value; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index a73ddde110..8e377ea632 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); } - public virtual void Update(Playfield playfield) + public void Update(Playfield playfield) { bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime); float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha; From 5e3ac5ac9f4a004bca348a1f23b17f0fc6e132ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 12:05:14 +0100 Subject: [PATCH 26/41] Reset combo in test in a less weird way --- osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index 13d78145b3..bbe543e73e 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods AddAssert("catcher must start visible", () => catcherAlphaAlmostEquals(1)); AddUntilStep("wait for combo", () => Player.ScoreProcessor.Combo.Value >= 2); AddAssert("catcher must dim after combo", () => !catcherAlphaAlmostEquals(1)); - AddStep("break combo", () => Player.ScoreProcessor.Combo.Set(0)); + AddStep("break combo", () => Player.ScoreProcessor.Combo.Value = 0); AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); } From 5e31e890ae8779a880f214c66aa64defacdae8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 12:36:23 +0100 Subject: [PATCH 27/41] Extract class for clipboard contents for DI purposes --- .../Editor/TestSceneManiaComposeScreen.cs | 5 ++--- .../Visual/Editing/TestSceneComposeScreen.cs | 5 ++--- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 7 ++++++- osu.Game/Screens/Edit/Editor.cs | 8 ++++---- osu.Game/Screens/Edit/EditorClipboard.cs | 15 +++++++++++++++ 5 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorClipboard.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 60a5051fb5..24d2a786a0 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -23,8 +22,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Resolved] private SkinManager skins { get; set; } - [Cached(Name = nameof(Screens.Edit.Editor.Clipboard))] - private Bindable clipboard = new Bindable(); + [Cached] + private EditorClipboard clipboard = new EditorClipboard(); [SetUpSteps] public void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 8796088ec6..9b8567e853 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; @@ -27,8 +26,8 @@ namespace osu.Game.Tests.Visual.Editing } }); - [Cached(Name = nameof(Editor.Clipboard))] - private Bindable clipboard = new Bindable(); + [Cached] + private EditorClipboard clipboard = new EditorClipboard(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index b0a85f33d9..d78dce5b60 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -28,7 +28,6 @@ namespace osu.Game.Screens.Edit.Compose [Resolved] private EditorClock clock { get; set; } - [Resolved(Name = nameof(Editor.Clipboard))] private Bindable clipboard { get; set; } private HitObjectComposer composer; @@ -77,6 +76,12 @@ namespace osu.Game.Screens.Edit.Compose return new EditorSkinProvidingContainer(EditorBeatmap).WithChild(content); } + [BackgroundDependencyLoader] + private void load(EditorClipboard clipboard) + { + this.clipboard = clipboard.Content.GetBoundCopy(); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b8a1dda508..de265ad94b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -103,8 +103,8 @@ namespace osu.Game.Screens.Edit [Resolved] private MusicController music { get; set; } - [Cached(Name = nameof(Clipboard))] - public readonly Bindable Clipboard = new Bindable(); + [Cached] + public readonly EditorClipboard Clipboard = new EditorClipboard(); public Editor(EditorLoader loader = null) { @@ -317,7 +317,7 @@ namespace osu.Game.Screens.Edit public void RestoreState([NotNull] EditorState state) => Schedule(() => { clock.Seek(state.Time); - Clipboard.Value = state.ClipboardContent; + Clipboard.Content.Value = state.ClipboardContent; }); protected void Save() @@ -743,7 +743,7 @@ namespace osu.Game.Screens.Edit protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState { Time = clock.CurrentTimeAccurate, - ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Value : string.Empty + ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty }); private void cancelExit() diff --git a/osu.Game/Screens/Edit/EditorClipboard.cs b/osu.Game/Screens/Edit/EditorClipboard.cs new file mode 100644 index 0000000000..f6f0c09e00 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorClipboard.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; + +namespace osu.Game.Screens.Edit +{ + /// + /// Wraps the contents of the editor clipboard. + /// + public class EditorClipboard + { + public Bindable Content { get; } = new Bindable(); + } +} From b25ad8dfcb498dea0068de61397108c2074daee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 12:49:04 +0100 Subject: [PATCH 28/41] Copy editor timestamp to OS clipboard even when triggered via menu bar Would only work when triggered via Ctrl+C before, and not work at all for Ctrl+X. --- .../Screens/Edit/Compose/ComposeScreen.cs | 49 ++++++------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index d78dce5b60..3b02d42b41 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -7,9 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -20,7 +17,7 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit.Compose { - public class ComposeScreen : EditorScreenWithTimeline, IKeyBindingHandler + public class ComposeScreen : EditorScreenWithTimeline { [Resolved] private GameHost host { get; set; } @@ -89,35 +86,6 @@ namespace osu.Game.Screens.Edit.Compose clipboard.BindValueChanged(_ => updateClipboardActionAvailability(), true); } - #region Input Handling - - public bool OnPressed(KeyBindingPressEvent e) - { - if (e.Action == PlatformAction.Copy) - host.GetClipboard()?.SetText(formatSelectionAsString()); - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - - private string formatSelectionAsString() - { - if (composer == null) - return string.Empty; - - double displayTime = EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).FirstOrDefault()?.StartTime ?? clock.CurrentTime; - string selectionAsString = composer.ConvertSelectionToString(); - - return !string.IsNullOrEmpty(selectionAsString) - ? $"{displayTime.ToEditorFormattedString()} ({selectionAsString}) - " - : $"{displayTime.ToEditorFormattedString()} - "; - } - - #endregion - #region Clipboard operations protected override void PerformCut() @@ -133,6 +101,8 @@ namespace osu.Game.Screens.Edit.Compose base.PerformCopy(); clipboard.Value = new ClipboardContent(EditorBeatmap).Serialize(); + + host.GetClipboard()?.SetText(formatSelectionAsString()); } protected override void PerformPaste() @@ -164,6 +134,19 @@ namespace osu.Game.Screens.Edit.Compose CanPaste.Value = !string.IsNullOrEmpty(clipboard.Value); } + private string formatSelectionAsString() + { + if (composer == null) + return string.Empty; + + double displayTime = EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).FirstOrDefault()?.StartTime ?? clock.CurrentTime; + string selectionAsString = composer.ConvertSelectionToString(); + + return !string.IsNullOrEmpty(selectionAsString) + ? $"{displayTime.ToEditorFormattedString()} ({selectionAsString}) - " + : $"{displayTime.ToEditorFormattedString()} - "; + } + #endregion } } From 34d235b790bf5686a1074a8217e5b2c1b79bd68a Mon Sep 17 00:00:00 2001 From: Semyon Rozhkov Date: Wed, 10 Nov 2021 15:50:36 +0300 Subject: [PATCH 29/41] Reset combo in different way --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index 0d0fefe0ff..8e226c7ded 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods AddAssert("cursor must start visible", () => cursorAlphaAlmostEquals(1)); AddUntilStep("wait for combo", () => Player.ScoreProcessor.Combo.Value >= 2); AddAssert("cursor must dim after combo", () => !cursorAlphaAlmostEquals(1)); - AddStep("break combo", () => Player.ScoreProcessor.Combo.Set(0)); + AddStep("break combo", () => Player.ScoreProcessor.Combo.Value = 0); AddUntilStep("wait for cursor to show", () => cursorAlphaAlmostEquals(1)); } From 30efc589d1a6b33e83402400e2d3f1f732b989a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 14:03:29 +0100 Subject: [PATCH 30/41] Fix logo sample always playing in main menu when initially logged out --- osu.Game/Screens/Menu/MainMenu.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 221b31f855..3da740b85d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -214,10 +215,16 @@ namespace osu.Game.Screens.Menu } else if (!api.IsLoggedIn) { - logo.Action += displayLogin; + // copy out old action to avoid accidentally capturing logo.Action in closure, causing a self-reference loop. + var previousAction = logo.Action; + + // we want to hook into logo.Action to display the login overlay, but also preserve the return value of the old action. + // therefore pass the old action to displayLogin, so that it can return that value. + // this ensures that the OsuLogo sample does not play when it is not desired. + logo.Action = () => displayLogin(previousAction); } - bool displayLogin() + bool displayLogin(Func originalAction) { if (!loginDisplayed.Value) { @@ -225,7 +232,7 @@ namespace osu.Game.Screens.Menu loginDisplayed.Value = true; } - return true; + return originalAction.Invoke(); } } From d370f64ac3e83ece673948b6d9d59804814da615 Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Wed, 10 Nov 2021 19:58:36 +0100 Subject: [PATCH 31/41] Changed finding the spinner gaps via a dictionary instead of getting the thresholds via an array --- .../Edit/Checks/CheckBananaShowerGap.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs index d6671cb9db..4b2933c0e1 100644 --- a/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs +++ b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks.Components; @@ -15,8 +16,15 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks /// public class CheckBananaShowerGap : ICheck { - private static readonly int[] spinner_start_delta_threshold = { 250, 250, 125, 125, 62, 62 }; - private static readonly int[] spinner_end_delta_threshold = { 250, 250, 250, 125, 125, 125 }; + private static readonly Dictionary spinner_delta_threshold = new Dictionary + { + [DifficultyRating.Easy] = (250, 250), + [DifficultyRating.Normal] = (250, 250), + [DifficultyRating.Hard] = (125, 250), + [DifficultyRating.Insane] = (125, 125), + [DifficultyRating.Expert] = (62, 125), + [DifficultyRating.ExpertPlus] = (62, 125) + }; public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Too short spinner gap"); @@ -29,9 +37,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { var hitObjects = context.Beatmap.HitObjects; - int interpretedDifficulty = (int)context.InterpretedDifficulty; - int expectedStartDelta = spinner_start_delta_threshold[interpretedDifficulty]; - int expectedEndDelta = spinner_end_delta_threshold[interpretedDifficulty]; + (int expectedStartDelta, int expectedEndDelta) = spinner_delta_threshold[context.InterpretedDifficulty]; for (int i = 0; i < hitObjects.Count - 1; ++i) { From 16418ac2ab69c1bb8b3b247a218016e4a08fe666 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 14:02:12 +0900 Subject: [PATCH 32/41] Remove outdated comments --- osu.Game/Online/Multiplayer/MatchRoomState.cs | 1 - osu.Game/Online/Multiplayer/MatchUserState.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MatchRoomState.cs b/osu.Game/Online/Multiplayer/MatchRoomState.cs index edd34fb5a3..30d948f878 100644 --- a/osu.Game/Online/Multiplayer/MatchRoomState.cs +++ b/osu.Game/Online/Multiplayer/MatchRoomState.cs @@ -16,7 +16,6 @@ namespace osu.Game.Online.Multiplayer [Serializable] [MessagePackObject] [Union(0, typeof(TeamVersusRoomState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. - // TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead). public abstract class MatchRoomState { } diff --git a/osu.Game/Online/Multiplayer/MatchUserState.cs b/osu.Game/Online/Multiplayer/MatchUserState.cs index 69245deba0..665b64a8b4 100644 --- a/osu.Game/Online/Multiplayer/MatchUserState.cs +++ b/osu.Game/Online/Multiplayer/MatchUserState.cs @@ -16,7 +16,6 @@ namespace osu.Game.Online.Multiplayer [Serializable] [MessagePackObject] [Union(0, typeof(TeamVersusUserState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. - // TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead). public abstract class MatchUserState { } From 98fa253e1ed82e6135ef97e8afbf6958494c8056 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 14:04:17 +0900 Subject: [PATCH 33/41] Replace usage of `TypeNameHandling.All` with custom type converter --- osu.Game/Online/HubClientConnector.cs | 8 ++- ...gnalRDerivedTypeWorkaroundJsonConverter.cs | 60 +++++++++++++++++++ .../Online/SignalRUnionWorkaroundResolver.cs | 17 +++++- 3 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index e9d6960c71..c79660568c 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -161,9 +162,10 @@ namespace osu.Game.Online builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - // TODO: This should only be required to be `TypeNameHandling.Auto`. - // See usage in osu-server-spectator for further documentation as to why this is required. - options.PayloadSerializerSettings.TypeNameHandling = TypeNameHandling.All; + options.PayloadSerializerSettings.Converters = new List + { + new SignalRDerivedTypeWorkaroundJsonConverter(), + }; }); } diff --git a/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs b/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs new file mode 100644 index 0000000000..55516d2223 --- /dev/null +++ b/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable +using System; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace osu.Game.Online +{ + /// + /// A type of that serializes a subset of types used in multiplayer/spectator communication that + /// derive from a known base type. This is a safe alternative to using or , + /// which are known to have security issues. + /// + public class SignalRDerivedTypeWorkaroundJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) => + SignalRUnionWorkaroundResolver.BASE_TYPES.Contains(objectType) || + SignalRUnionWorkaroundResolver.DERIVED_TYPES.Contains(objectType); + + public override object? ReadJson(JsonReader reader, Type objectType, object? o, JsonSerializer jsonSerializer) + { + if (reader.TokenType == JsonToken.Null) + return null; + + JObject obj = JObject.Load(reader); + + string type = (string)obj[@"$dtype"]!; + + var resolvedType = SignalRUnionWorkaroundResolver.DERIVED_TYPES.Single(t => t.Name == type); + + object? instance = Activator.CreateInstance(resolvedType); + + jsonSerializer.Populate(obj["$value"]!.CreateReader(), instance); + + return instance; + } + + public override void WriteJson(JsonWriter writer, object? o, JsonSerializer serializer) + { + if (o == null) + { + writer.WriteNull(); + return; + } + + writer.WriteStartObject(); + + writer.WritePropertyName(@"$dtype"); + serializer.Serialize(writer, o.GetType().Name); + + writer.WritePropertyName(@"$value"); + writer.WriteRawValue(JsonConvert.SerializeObject(o)); + + writer.WriteEndObject(); + } + } +} diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs index e44da044cc..21413f8285 100644 --- a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs +++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs @@ -20,7 +20,22 @@ namespace osu.Game.Online public static readonly MessagePackSerializerOptions OPTIONS = MessagePackSerializerOptions.Standard.WithResolver(new SignalRUnionWorkaroundResolver()); - private static readonly Dictionary formatter_map = new Dictionary + public static readonly IReadOnlyList BASE_TYPES = new[] + { + typeof(MatchServerEvent), + typeof(MatchUserRequest), + typeof(MatchRoomState), + typeof(MatchUserState), + }; + + public static readonly IReadOnlyList DERIVED_TYPES = new[] + { + typeof(ChangeTeamRequest), + typeof(TeamVersusRoomState), + typeof(TeamVersusUserState), + }; + + private static readonly IReadOnlyDictionary formatter_map = new Dictionary { { typeof(TeamVersusUserState), new TypeRedirectingFormatter() }, { typeof(TeamVersusRoomState), new TypeRedirectingFormatter() }, From ea536dea23c9b6cfe75693b679304ff416aa7688 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 14:45:40 +0900 Subject: [PATCH 34/41] Gracefully handle missing type rather than triggering `ArgumentNullException` --- osu.Game/Rulesets/RulesetStore.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 391bf2c07d..914f33a7ae 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -129,13 +129,18 @@ namespace osu.Game.Rulesets { try { - var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo).AsNonNull())).RulesetInfo; + var resolvedType = Type.GetType(r.InstantiationInfo); - r.Name = instanceInfo.Name; - r.ShortName = instanceInfo.ShortName; - r.InstantiationInfo = instanceInfo.InstantiationInfo; + if (resolvedType != null) + { + var instanceInfo = ((Ruleset)Activator.CreateInstance(resolvedType)).RulesetInfo; - r.Available = true; + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; + + r.Available = true; + } } catch { From 57c333b472f724c24aab22e1890ed0b993926755 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 11 Nov 2021 15:29:08 +0900 Subject: [PATCH 35/41] Remove unused using --- osu.Game/Rulesets/RulesetStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 914f33a7ae..1ddd5396f4 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Reflection; using osu.Framework; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; From f38d6ef8db67229dc7a4f440aa63be23d7a6c882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 08:39:59 +0100 Subject: [PATCH 36/41] Add failing test steps --- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 13 +++++++++++-- .../Visual/Multiplayer/TestMultiplayerClient.cs | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 1efa8d6810..99ff307235 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -10,6 +10,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -97,16 +98,24 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddStep("add another user", () => client.AddUser(new APIUser { Username = "otheruser", Id = 44 })); - AddStep("press button", () => + AddStep("press own button", () => { InputManager.MoveMouseTo(multiplayerScreenStack.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); AddAssert("user on team 1", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1); - AddStep("press button", () => InputManager.Click(MouseButton.Left)); + AddStep("press own button again", () => InputManager.Click(MouseButton.Left)); AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + + AddStep("press other user's button", () => + { + InputManager.MoveMouseTo(multiplayerScreenStack.ChildrenOfType().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + AddAssert("user still on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); } [Test] diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 0860e45346..f81ca70631 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -71,6 +71,21 @@ namespace osu.Game.Tests.Visual.Multiplayer // We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation. Scheduler.Update(); + + switch (Room?.MatchState) + { + case TeamVersusRoomState teamVersus: + Debug.Assert(Room != null); + + // simulate the server's automatic assignment of users to teams on join. + // the "best" team is the one with the least users on it. + int bestTeam = teamVersus.Teams + .Select(team => (teamID: team.ID, userCount: Room.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) + .OrderBy(pair => pair.userCount) + .First().teamID; + ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState { TeamID = bestTeam }).Wait(); + break; + } } public void RemoveUser(APIUser user) From 9664b9a97ca60957c2137e7c08b59c2ed12792ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 08:40:50 +0100 Subject: [PATCH 37/41] Fix being able to switch own team by clicking other players' team indicators --- .../Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index 1bf62241f2..73aca0acdc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -51,7 +51,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Alpha = 0, Scale = new Vector2(0, 1), RelativeSizeAxes = Axes.Y, - Action = changeTeam, Child = box = new Container { RelativeSizeAxes = Axes.Both, From ebe58cee1129a01d7c9c420cbdb94a16751e9f08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 17:18:51 +0900 Subject: [PATCH 38/41] Rename `BeatmapInfo.StarDifficulty` to `StarRating` to match underlying interface --- .../NonVisual/Filtering/FilterMatchingTest.cs | 2 +- .../Visual/Multiplayer/TestSceneDrawableRoom.cs | 8 ++++---- .../Multiplayer/TestSceneStarRatingRangeDisplay.cs | 4 ++-- .../Visual/SongSelect/TestSceneAdvancedStats.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 10 +++++----- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 2 +- .../SongSelect/TestSceneBeatmapMetadataDisplay.cs | 4 ++-- .../SongSelect/TestSceneBeatmapRecommendations.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 8 +++----- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- osu.Game/Beatmaps/DifficultyRecommender.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 6 +++--- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 16 files changed, 30 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 41939cec3f..743d11541d 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.NonVisual.Filtering private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { Ruleset = new RulesetInfo { ID = 5 }, - StarDifficulty = 4.0d, + StarRating = 4.0d, BaseDifficulty = new BeatmapDifficulty { ApproachRate = 5.0f, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 721862c177..2c28a1752e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { BeatmapInfo = { - StarDifficulty = 2.5 + StarRating = 2.5 } }.BeatmapInfo, } @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { BeatmapInfo = { - StarDifficulty = 2.5, + StarRating = 2.5, Metadata = { Artist = "very very very very very very very very very long artist", @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { BeatmapInfo = { - StarDifficulty = 2.5 + StarRating = 2.5 } }.BeatmapInfo, } @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { BeatmapInfo = { - StarDifficulty = 4.5 + StarRating = 4.5 } }.BeatmapInfo, } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs index cdeafdc9a3..20db922122 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -31,8 +31,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value.Playlist.AddRange(new[] { - new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = min } } }, - new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = max } } }, + new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarRating = min } } }, + new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarRating = max } } }, }); }); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 07e68ef509..d57b3dec5d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.SongSelect OverallDifficulty = 5.7f, ApproachRate = 3.5f }, - StarDifficulty = 4.5f + StarRating = 4.5f }; [Test] @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.SongSelect OverallDifficulty = 4.5f, ApproachRate = 3.1f }, - StarDifficulty = 8 + StarRating = 8 }); AddAssert("first bar text is Key Count", () => advancedStats.ChildrenOfType().First().Text == "Key Count"); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 9a142f3ca8..3a5af6811d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -435,8 +435,8 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { var set = createTestBeatmapSet(i); - set.Beatmaps[0].StarDifficulty = 3 - i; - set.Beatmaps[2].StarDifficulty = 6 + i; + set.Beatmaps[0].StarRating = 3 - i; + set.Beatmaps[2].StarRating = 6 + i; sets.Add(set); } @@ -686,7 +686,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Version = $"Stars: {i}", Ruleset = new OsuRuleset().RulesetInfo, - StarDifficulty = i, + StarRating = i, }); } @@ -869,7 +869,7 @@ namespace osu.Game.Tests.Visual.SongSelect { OnlineBeatmapID = id++ * 10, Version = version, - StarDifficulty = diff, + StarRating = diff, Ruleset = new OsuRuleset().RulesetInfo, BaseDifficulty = new BeatmapDifficulty { @@ -904,7 +904,7 @@ namespace osu.Game.Tests.Visual.SongSelect Path = $"extra{b}.osu", Version = $"Extra {b}", Ruleset = rulesets.GetRuleset((b - 1) % 4), - StarDifficulty = 2, + StarRating = 2, BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 3.5f, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 9fa0eab548..666969eb2a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.SongSelect Title = $"{ruleset.ShortName}Title" }, Ruleset = ruleset, - StarDifficulty = 6, + StarRating = 6, Version = $"{ruleset.ShortName}Version", BaseDifficulty = new BeatmapDifficulty() }, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 50ae673c06..574ccce6d5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.SongSelect Title = title, }, Version = version, - StarDifficulty = RNG.NextDouble(0, 10), + StarRating = RNG.NextDouble(0, 10), } })); } @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.SongSelect Title = "Heavy beatmap", }, Version = "10k objects", - StarDifficulty = 99.99f, + StarRating = 99.99f, } })); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 68d5836cac..2c41ea1f53 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.SongSelect Metadata = metadata, BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, - StarDifficulty = difficultyIndex + 1, + StarRating = difficultyIndex + 1, Version = $"SR{difficultyIndex + 1}" }).ToList() }; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 01a819dead..613b0599a0 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -139,7 +139,8 @@ namespace osu.Game.Beatmaps private string versionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; [JsonProperty("difficulty_rating")] - public double StarDifficulty { get; set; } + [Column("StarDifficulty")] + public double StarRating { get; set; } /// /// Currently only populated for beatmap deletion. Use to query scores. @@ -147,7 +148,7 @@ namespace osu.Game.Beatmaps public List Scores { get; set; } [JsonIgnore] - public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarDifficulty); + public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarRating); public override string ToString() => this.GetDisplayTitle(); @@ -197,9 +198,6 @@ namespace osu.Game.Beatmaps [JsonIgnore] IRulesetInfo IBeatmapInfo.Ruleset => Ruleset; - [JsonIgnore] - double IBeatmapInfo.StarRating => StarDifficulty; - #endregion } } diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index a654b05edb..d166f42cde 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -407,7 +407,7 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Ruleset = ruleset; // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; + beatmap.BeatmapInfo.StarRating = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; beatmap.BeatmapInfo.Length = calculateLength(beatmap); beatmap.BeatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength(); diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 638366c580..0c93c4b9db 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -38,7 +38,7 @@ namespace osu.Game.Beatmaps /// /// The maximum star difficulty of all beatmaps in this set. /// - public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0; + public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarRating) ?? 0; /// /// The maximum playable length in milliseconds of all beatmaps in this set. diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 86f5e0dabf..a2bd7c6ce9 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b => { - double difference = b.StarDifficulty - recommendation; + double difference = b.StarRating - recommendation; return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder }).FirstOrDefault(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index de265ad94b..0ca7038580 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -723,7 +723,7 @@ namespace osu.Game.Screens.Edit if (difficultyItems.Count > 0) difficultyItems.Add(new EditorMenuItemSpacer()); - foreach (var beatmap in rulesetBeatmaps.OrderBy(b => b.StarDifficulty)) + foreach (var beatmap in rulesetBeatmaps.OrderBy(b => b.StarRating)) difficultyItems.Add(createDifficultyMenuItem(beatmap)); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index fa96e6dde7..1334784613 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select.Carousel return; } - match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarDifficulty); + match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarRating); match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.BaseDifficulty.ApproachRate); match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.BaseDifficulty.DrainRate); match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.BaseDifficulty.CircleSize); @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) || criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode); - match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarDifficulty); + match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); if (match) { @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Select.Carousel int ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); if (ruleset != 0) return ruleset; - return BeatmapInfo.StarDifficulty.CompareTo(otherBeatmap.BeatmapInfo.StarDifficulty); + return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index e465f423bc..9e411d5daa 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Select.Carousel return compareUsingAggregateMax(otherSet, b => b.Length); case SortMode.Difficulty: - return compareUsingAggregateMax(otherSet, b => b.StarDifficulty); + return compareUsingAggregateMax(otherSet, b => b.StarRating); } } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 83e1423504..55a3a6874e 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -228,7 +228,7 @@ namespace osu.Game.Tests.Visual Checksum = beatmap.MD5Hash, AuthorID = beatmap.Metadata.Author.OnlineID, RulesetID = beatmap.RulesetID, - StarRating = beatmap.StarDifficulty, + StarRating = beatmap.StarRating, DifficultyName = beatmap.Version, } } From 51a353e12db189f9958228d30fe045b8460c6b92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 17:19:53 +0900 Subject: [PATCH 39/41] Rename `BeatmapInfo.Version` to `DifficultyName` to match underlying interface --- .../Editor/TestSceneEditorSaving.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 6 +++--- osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs | 2 +- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 4 ++-- .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 2 +- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 6 +++--- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapRecommendations.cs | 2 +- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 8 ++------ osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- .../Screens/Edit/Components/Menus/DifficultyMenuItem.cs | 2 +- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 4 ++-- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 2 +- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 26 files changed, 37 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs index 159a64d1ac..42ab84714a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; editorBeatmap.BeatmapInfo.Metadata.Title = "title"; }); - AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.Version = "difficulty"); + AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); checkMutations(); @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2); }); AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title"); - AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.Version == "difficulty"); + AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); } } } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 0bd7c19200..304a65e5c7 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("Soleily", metadata.Artist); Assert.AreEqual("Soleily", metadata.ArtistUnicode); Assert.AreEqual("Gamu", metadata.Author.Username); - Assert.AreEqual("Insane", beatmapInfo.Version); + Assert.AreEqual("Insane", beatmapInfo.DifficultyName); Assert.AreEqual(string.Empty, metadata.Source); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID); diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index a19e977c1a..3093a5719d 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -790,12 +790,12 @@ namespace osu.Game.Tests.Beatmaps.IO // Update via the beatmap, not the beatmap info, to ensure correct linking BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap; - beatmapToUpdate.BeatmapInfo.Version = "updated"; + beatmapToUpdate.BeatmapInfo.DifficultyName = "updated"; manager.Update(setToUpdate); BeatmapInfo updatedInfo = manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID); - Assert.That(updatedInfo.Version, Is.EqualTo("updated")); + Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated")); } finally { @@ -863,7 +863,7 @@ namespace osu.Game.Tests.Beatmaps.IO var beatmap = working.Beatmap; - beatmap.BeatmapInfo.Version = "difficulty"; + beatmap.BeatmapInfo.DifficultyName = "difficulty"; beatmap.BeatmapInfo.Metadata = new BeatmapMetadata { Artist = "Artist/With\\Slashes", diff --git a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs index da7f32a2d3..4a7d7505ad 100644 --- a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs +++ b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps Title = "title", Author = new APIUser { Username = "creator" } }, - Version = "difficulty" + DifficultyName = "difficulty" }; Assert.That(beatmap.ToString(), Is.EqualTo("artist - title (creator) [difficulty]")); diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 743d11541d..ec97948532 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Source = "unit tests", Tags = "look for tags too", }, - Version = "version as well", + DifficultyName = "version as well", Length = 2500, BPM = 160, BeatDivisor = 12, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index af3d9beb69..e1e869cfbf 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editing editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; editorBeatmap.BeatmapInfo.Metadata.Title = "title"; }); - AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.Version = "difficulty"); + AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7); AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title"); - AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.Version == "difficulty"); + AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index ba30315663..5e55759e01 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Ruleset = new OsuRuleset().RulesetInfo, OnlineBeatmapID = beatmapId, - Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", + DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 3a5af6811d..03079fdc5f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -684,7 +684,7 @@ namespace osu.Game.Tests.Visual.SongSelect { set.Beatmaps.Add(new BeatmapInfo { - Version = $"Stars: {i}", + DifficultyName = $"Stars: {i}", Ruleset = new OsuRuleset().RulesetInfo, StarRating = i, }); @@ -868,7 +868,7 @@ namespace osu.Game.Tests.Visual.SongSelect yield return new BeatmapInfo { OnlineBeatmapID = id++ * 10, - Version = version, + DifficultyName = version, StarRating = diff, Ruleset = new OsuRuleset().RulesetInfo, BaseDifficulty = new BeatmapDifficulty @@ -902,7 +902,7 @@ namespace osu.Game.Tests.Visual.SongSelect { OnlineBeatmapID = b * 10, Path = $"extra{b}.osu", - Version = $"Extra {b}", + DifficultyName = $"Extra {b}", Ruleset = rulesets.GetRuleset((b - 1) % 4), StarRating = 2, BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 666969eb2a..1b070c00bf 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -199,7 +199,7 @@ namespace osu.Game.Tests.Visual.SongSelect }, Ruleset = ruleset, StarRating = 6, - Version = $"{ruleset.ShortName}Version", + DifficultyName = $"{ruleset.ShortName}Version", BaseDifficulty = new BeatmapDifficulty() }, HitObjects = objects @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.SongSelect Source = "Verrrrry long Source", Title = "Verrrrry long Title" }, - Version = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version", + DifficultyName = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version", Status = BeatmapSetOnlineStatus.Graveyard, }, }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 574ccce6d5..9473b058cc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Title = title, }, - Version = version, + DifficultyName = version, StarRating = RNG.NextDouble(0, 10), } })); @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Title = "Heavy beatmap", }, - Version = "10k objects", + DifficultyName = "10k objects", StarRating = 99.99f, } })); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 2c41ea1f53..57f2d436c5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.SongSelect BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, StarRating = difficultyIndex + 1, - Version = $"SR{difficultyIndex + 1}" + DifficultyName = $"SR{difficultyIndex + 1}" }).ToList() }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 4861354921..a0d78fff58 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -919,7 +919,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Ruleset = getRuleset(), OnlineBeatmapID = beatmapId, - Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", + DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 07759d598e..7353e47229 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.UserInterface Username = "TestAuthor" }, }, - Version = "Insane" + DifficultyName = "Insane" }, } }, diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 98087994b7..f69a10f000 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -56,7 +56,7 @@ namespace osu.Game.Beatmaps Title = @"Unknown", AuthorString = @"Unknown Creator", }, - Version = @"Normal", + DifficultyName = @"Normal", BaseDifficulty = Difficulty, }; } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 613b0599a0..5bbd48f26d 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -134,9 +134,8 @@ namespace osu.Game.Beatmaps public double TimelineZoom { get; set; } // Metadata - public string Version { get; set; } - - private string versionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; + [Column("Version")] + public string DifficultyName { get; set; } [JsonProperty("difficulty_rating")] [Column("StarDifficulty")] @@ -183,9 +182,6 @@ namespace osu.Game.Beatmaps #region Implementation of IBeatmapInfo - [JsonIgnore] - string IBeatmapInfo.DifficultyName => Version; - [JsonIgnore] IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSet?.Metadata ?? new BeatmapMetadata(); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index d166f42cde..ff4305dc91 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -215,7 +215,7 @@ namespace osu.Game.Beatmaps var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo(); // metadata may have changed; update the path with the standard format. - beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu"); + beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu"); beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index bef2d78f21..f0c19f80f3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -251,7 +251,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Version": - beatmap.BeatmapInfo.Version = pair.Value; + beatmap.BeatmapInfo.DifficultyName = pair.Value; break; case @"Source": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 9117da5d32..bcb14526c7 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -130,7 +130,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}")); if (!string.IsNullOrEmpty(beatmap.Metadata.ArtistUnicode)) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}")); writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.Author.Username}")); - writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}")); + writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.DifficultyName}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Source)) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Tags)) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}")); diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs index c458b65607..75dc479c25 100644 --- a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Components.Menus public BeatmapInfo BeatmapInfo { get; } public DifficultyMenuItem(BeatmapInfo beatmapInfo, bool selected, Action difficultyChangeFunc) - : base(beatmapInfo.Version ?? "(unnamed)", null) + : base(beatmapInfo.DifficultyName ?? "(unnamed)", null) { BeatmapInfo = beatmapInfo; State.Value = selected; diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 34c2fa8480..0d2b093a2e 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit.Setup Empty(), creatorTextBox = createTextBox("Creator", metadata.Author.Username), - difficultyTextBox = createTextBox("Difficulty Name", Beatmap.BeatmapInfo.Version), + difficultyTextBox = createTextBox("Difficulty Name", Beatmap.BeatmapInfo.DifficultyName), sourceTextBox = createTextBox("Source", metadata.Source), tagsTextBox = createTextBox("Tags", metadata.Tags) }; @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Metadata.Title = RomanisedTitleTextBox.Current.Value; Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value; - Beatmap.BeatmapInfo.Version = difficultyTextBox.Current.Value; + Beatmap.BeatmapInfo.DifficultyName = difficultyTextBox.Current.Value; Beatmap.Metadata.Source = sourceTextBox.Current.Value; Beatmap.Metadata.Tags = tagsTextBox.Current.Value; } diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 909f0a2b65..430571e1da 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Play { new OsuSpriteText { - Text = beatmap?.BeatmapInfo?.Version, + Text = beatmap?.BeatmapInfo?.DifficultyName, Font = OsuFont.GetFont(size: 26, italics: true), Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 29b9d6164e..e31a182a49 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Ranking.Expanded { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = beatmap.Version, + Text = beatmap.DifficultyName, Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold), }, new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12)) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index e344da4027..e4cf9bd868 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Select { VersionLabel = new OsuSpriteText { - Text = beatmapInfo.Version, + Text = beatmapInfo.DifficultyName, Font = OsuFont.GetFont(size: 24, italics: true), RelativeSizeAxes = Axes.X, Truncate = true, @@ -324,7 +324,7 @@ namespace osu.Game.Screens.Select }); // no difficulty means it can't have a status to show - if (beatmapInfo.Version == null) + if (beatmapInfo.DifficultyName == null) StatusPill.Hide(); addInfoLabels(); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 5940911d4a..f30bec5d2b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Select.Carousel { new OsuSpriteText { - Text = beatmapInfo.Version, + Text = beatmapInfo.DifficultyName, Font = OsuFont.GetFont(size: 20), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 1ac73cf781..01bf925547 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -253,7 +253,7 @@ namespace osu.Game.Stores var beatmap = new RealmBeatmap(ruleset, difficulty, metadata) { Hash = hash, - DifficultyName = decodedInfo.Version, + DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineBeatmapID ?? -1, AudioLeadIn = decodedInfo.AudioLeadIn, StackLeniency = decodedInfo.StackLeniency, diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 55a3a6874e..a4cef02395 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -229,7 +229,7 @@ namespace osu.Game.Tests.Visual AuthorID = beatmap.Metadata.Author.OnlineID, RulesetID = beatmap.RulesetID, StarRating = beatmap.StarRating, - DifficultyName = beatmap.Version, + DifficultyName = beatmap.DifficultyName, } } }; From 4bca96d548b72725164ffdc73844ac4644bb26af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 17:37:43 +0900 Subject: [PATCH 40/41] Throw again to ensure correct available state is set Also standardises handling between `RulesetStore` and `RealmRulesetStore`. --- osu.Game/Rulesets/RulesetLoadException.cs | 15 +++++++++++++++ osu.Game/Rulesets/RulesetStore.cs | 18 ++++++++---------- osu.Game/Stores/RealmRulesetStore.cs | 18 +++++++----------- 3 files changed, 30 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Rulesets/RulesetLoadException.cs diff --git a/osu.Game/Rulesets/RulesetLoadException.cs b/osu.Game/Rulesets/RulesetLoadException.cs new file mode 100644 index 0000000000..7c3a4bb75d --- /dev/null +++ b/osu.Game/Rulesets/RulesetLoadException.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Rulesets +{ + public class RulesetLoadException : Exception + { + public RulesetLoadException(string message) + : base(@$"Ruleset could not be loaded ({message})") + { + } + } +} diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 1ddd5396f4..6dd036c0e6 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -128,18 +128,16 @@ namespace osu.Game.Rulesets { try { - var resolvedType = Type.GetType(r.InstantiationInfo); + var resolvedType = Type.GetType(r.InstantiationInfo) + ?? throw new RulesetLoadException(@"Type could not be resolved"); - if (resolvedType != null) - { - var instanceInfo = ((Ruleset)Activator.CreateInstance(resolvedType)).RulesetInfo; + var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo + ?? throw new RulesetLoadException(@"Instantiation failure"); - r.Name = instanceInfo.Name; - r.ShortName = instanceInfo.ShortName; - r.InstantiationInfo = instanceInfo.InstantiationInfo; - - r.Available = true; - } + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; + r.Available = true; } catch { diff --git a/osu.Game/Stores/RealmRulesetStore.cs b/osu.Game/Stores/RealmRulesetStore.cs index 0d2cddf874..cf9ffd112c 100644 --- a/osu.Game/Stores/RealmRulesetStore.cs +++ b/osu.Game/Stores/RealmRulesetStore.cs @@ -147,19 +147,15 @@ namespace osu.Game.Stores { try { - var type = Type.GetType(r.InstantiationInfo); + var resolvedType = Type.GetType(r.InstantiationInfo) + ?? throw new RulesetLoadException(@"Type could not be resolved"); - if (type == null) - throw new InvalidOperationException(@"Type resolution failure."); + var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo + ?? throw new RulesetLoadException(@"Instantiation failure"); - var rInstance = (Activator.CreateInstance(type) as Ruleset)?.RulesetInfo; - - if (rInstance == null) - throw new InvalidOperationException(@"Instantiation failure."); - - r.Name = rInstance.Name; - r.ShortName = rInstance.ShortName; - r.InstantiationInfo = rInstance.InstantiationInfo; + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; r.Available = true; detachedRulesets.Add(r.Clone()); From 53e52d2c4b8f32eb7ce07cb7c2ed8a8cae05c5a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 10:04:16 +0100 Subject: [PATCH 41/41] Fix Android builds failing due to Xamarin-side regression --- .github/workflows/ci.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a3b2fd978..68f8ef51ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,22 +52,29 @@ jobs: build-only-android: name: Build only (Android) - runs-on: windows-latest + runs-on: macos-latest timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v2 + # Pin Xamarin.Android version to 11.2 for now to avoid build failures caused by a Xamarin-side regression. + # See: https://github.com/xamarin/xamarin-android/issues/6284 + # This can be removed/reverted when the fix makes it to upstream and is deployed on github runners. + - name: Set default Xamarin SDK version + run: | + $VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.2 + - name: Install .NET 5.0.x uses: actions/setup-dotnet@v1 with: dotnet-version: "5.0.x" - - name: Setup MSBuild - uses: microsoft/setup-msbuild@v1 - + # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono + # cannot accept .sln(f) files as arguments. + # Build just the main game for now. - name: Build - run: msbuild osu.Android.slnf /restore /p:Configuration=Debug + run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug build-only-ios: # While this workflow technically *can* run, it fails as iOS builds are blocked by multiple issues.