diff --git a/README.md b/README.md index 24b70b2de6..f18c5e76f9 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ You can see some examples of custom rulesets by visiting the [custom ruleset dir Please make sure you have the following prerequisites: -- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) or higher installed. +- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) installed. - When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). - When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). - When running on Linux, please have a system-wide FFmpeg installation available to support video decoding. diff --git a/osu.Android.props b/osu.Android.props index 209b8cd63e..ca4d88a8a7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs index dbfd170ea1..4acaf61cea 100644 --- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -14,6 +14,7 @@ namespace osu.Desktop.Windows { private Bindable disableWinKey; private IBindable localUserPlaying; + private IBindable isActive; [Resolved] private GameHost host { get; set; } @@ -24,13 +25,16 @@ namespace osu.Desktop.Windows localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy(); localUserPlaying.BindValueChanged(_ => updateBlocking()); + isActive = host.IsActive.GetBoundCopy(); + isActive.BindValueChanged(_ => updateBlocking()); + disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); disableWinKey.BindValueChanged(_ => updateBlocking(), true); } private void updateBlocking() { - bool shouldDisable = disableWinKey.Value && localUserPlaying.Value; + bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value; if (shouldDisable) host.InputThread.Scheduler.Add(WindowsKey.Disable); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 44ba0e2057..03abba29ce 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { } - private double skillMultiplier => 0.15; + private double skillMultiplier => 0.07; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -40,26 +40,31 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double result = 0.0; + OsuDifficultyHitObject lastObj = osuCurrent; + + // This is iterating backwards in time from the current object. for (int i = 0; i < Previous.Count; i++) { - var osuPrevious = (OsuDifficultyHitObject)Previous[i]; - var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject); + var currentObj = (OsuDifficultyHitObject)Previous[i]; + var currentHitObject = (OsuHitObject)(currentObj.BaseObject); - if (!(osuPrevious.BaseObject is Spinner)) + if (!(currentObj.BaseObject is Spinner)) { - double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length; + double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length; - cumulativeStrainTime += osuPrevious.StrainTime; + cumulativeStrainTime += lastObj.StrainTime; // We want to nerf objects that can be easily seen within the Flashlight circle radius. if (i == 0) smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); // We also want to nerf stacks so that only the first object of the stack is accounted for. - double stackNerf = Math.Min(1.0, (osuPrevious.LazyJumpDistance / scalingFactor) / 25.0); + double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); - result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; + result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } + + lastObj = currentObj; } return Math.Pow(smallDistNerf * result, 2.0); diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 8a063b3c6e..8d15be44fa 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -61,6 +61,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -94,6 +95,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -160,6 +162,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -168,7 +171,7 @@ namespace osu.Game.Tests.NonVisual public void TestMigrationBetweenTwoTargets() { string customPath = prepareCustomPath(); - string customPath2 = prepareCustomPath("-2"); + string customPath2 = prepareCustomPath(); using (var host = new CustomTestHeadlessGameHost()) { @@ -185,7 +188,7 @@ namespace osu.Game.Tests.NonVisual Assert.That(File.Exists(Path.Combine(customPath2, database_filename))); // some files may have been left behind for whatever reason, but that's not what we're testing here. - customPath = prepareCustomPath(); + cleanupPath(customPath); Assert.DoesNotThrow(() => osu.Migrate(customPath)); Assert.That(File.Exists(Path.Combine(customPath, database_filename))); @@ -193,6 +196,8 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); + cleanupPath(customPath2); } } } @@ -214,6 +219,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -243,6 +249,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -272,6 +279,7 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); + cleanupPath(customPath); } } } @@ -286,14 +294,18 @@ namespace osu.Game.Tests.NonVisual return path; } - private string prepareCustomPath(string suffix = "") + private static string prepareCustomPath() => Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path-{Guid.NewGuid()}"); + + private static void cleanupPath(string path) { - string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path{suffix}"); - - if (Directory.Exists(path)) - Directory.Delete(path, true); - - return path; + try + { + if (Directory.Exists(path)) + Directory.Delete(path, true); + } + catch + { + } } public class CustomTestHeadlessGameHost : CleanRunHeadlessGameHost diff --git a/osu.Game.Tests/NonVisual/SessionStaticsTest.cs b/osu.Game.Tests/NonVisual/SessionStaticsTest.cs index d5fd803986..cd02f15adf 100644 --- a/osu.Game.Tests/NonVisual/SessionStaticsTest.cs +++ b/osu.Game.Tests/NonVisual/SessionStaticsTest.cs @@ -1,9 +1,10 @@ // 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 NUnit.Framework; using osu.Game.Configuration; -using osu.Game.Input; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Tests.NonVisual { @@ -11,37 +12,32 @@ namespace osu.Game.Tests.NonVisual public class SessionStaticsTest { private SessionStatics sessionStatics; - private IdleTracker sessionIdleTracker; - [SetUp] - public void SetUp() + [Test] + public void TestSessionStaticsReset() { sessionStatics = new SessionStatics(); - sessionIdleTracker = new GameIdleTracker(1000); sessionStatics.SetValue(Static.LoginOverlayDisplayed, true); sessionStatics.SetValue(Static.MutedAudioNotificationShownOnce, true); sessionStatics.SetValue(Static.LowBatteryNotificationShownOnce, true); sessionStatics.SetValue(Static.LastHoverSoundPlaybackTime, (double?)1d); + sessionStatics.SetValue(Static.SeasonalBackgrounds, new APISeasonalBackgrounds { EndDate = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.Zero) }); - sessionIdleTracker.IsIdle.BindValueChanged(e => - { - if (e.NewValue) - sessionStatics.ResetValues(); - }); - } + Assert.IsFalse(sessionStatics.GetBindable(Static.LoginOverlayDisplayed).IsDefault); + Assert.IsFalse(sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).IsDefault); + Assert.IsFalse(sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).IsDefault); + Assert.IsFalse(sessionStatics.GetBindable(Static.LastHoverSoundPlaybackTime).IsDefault); + Assert.IsFalse(sessionStatics.GetBindable(Static.SeasonalBackgrounds).IsDefault); - [Test] - [Timeout(2000)] - public void TestSessionStaticsReset() - { - sessionIdleTracker.IsIdle.BindValueChanged(e => - { - Assert.IsTrue(sessionStatics.GetBindable(Static.LoginOverlayDisplayed).IsDefault); - Assert.IsTrue(sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).IsDefault); - Assert.IsTrue(sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).IsDefault); - Assert.IsTrue(sessionStatics.GetBindable(Static.LastHoverSoundPlaybackTime).IsDefault); - }); + sessionStatics.ResetAfterInactivity(); + + Assert.IsTrue(sessionStatics.GetBindable(Static.LoginOverlayDisplayed).IsDefault); + Assert.IsTrue(sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).IsDefault); + Assert.IsTrue(sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).IsDefault); + // some statics should not reset despite inactivity. + Assert.IsFalse(sessionStatics.GetBindable(Static.LastHoverSoundPlaybackTime).IsDefault); + Assert.IsFalse(sessionStatics.GetBindable(Static.SeasonalBackgrounds).IsDefault); } } } diff --git a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs new file mode 100644 index 0000000000..d33081662d --- /dev/null +++ b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Game.Online.Rooms; + +namespace osu.Game.Tests.OnlinePlay +{ + [TestFixture] + public class PlaylistExtensionsTest + { + [Test] + public void TestEmpty() + { + // mostly an extreme edge case, i.e. during room creation. + var items = Array.Empty(); + + Assert.Multiple(() => + { + Assert.That(items.GetHistoricalItems(), Is.Empty); + Assert.That(items.GetCurrentItem(), Is.Null); + Assert.That(items.GetUpcomingItems(), Is.Empty); + }); + } + + [Test] + public void TestPlaylistItemsInOrder() + { + var items = new[] + { + new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 }, + new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 }, + new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + }; + + Assert.Multiple(() => + { + Assert.That(items.GetHistoricalItems(), Is.Empty); + Assert.That(items.GetCurrentItem(), Is.EqualTo(items[0])); + Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(items)); + }); + } + + [Test] + public void TestPlaylistItemsOutOfOrder() + { + var items = new[] + { + new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 }, + new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 }, + new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + }; + + Assert.Multiple(() => + { + Assert.That(items.GetHistoricalItems(), Is.Empty); + Assert.That(items.GetCurrentItem(), Is.EqualTo(items[1])); + Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(new[] { items[1], items[0], items[2] })); + }); + } + + [Test] + public void TestExpiredPlaylistItemsSkipped() + { + var items = new[] + { + new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) }, + new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) }, + new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + }; + + Assert.Multiple(() => + { + Assert.That(items.GetHistoricalItems(), Is.EquivalentTo(new[] { items[1], items[0] })); + Assert.That(items.GetCurrentItem(), Is.EqualTo(items[2])); + Assert.That(items.GetUpcomingItems(), Is.EquivalentTo(new[] { items[2] })); + }); + } + + [Test] + public void TestAllItemsExpired() + { + var items = new[] + { + new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) }, + new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) }, + new PlaylistItem { ID = 3, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 57, 0, TimeSpan.Zero) }, + }; + + Assert.Multiple(() => + { + Assert.That(items.GetHistoricalItems(), Is.EquivalentTo(new[] { items[1], items[0], items[2] })); + // if all items are expired, the last-played item is expected to be returned. + Assert.That(items.GetCurrentItem(), Is.EqualTo(items[2])); + Assert.That(items.GetUpcomingItems(), Is.Empty); + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index 3211405670..844fe7705a 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -85,6 +85,7 @@ namespace osu.Game.Tests.Visual.Background // of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack. AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value))); AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen()); + AddUntilStep("previous background hidden", () => !screen.IsAlive); AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null); diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 0b9857486a..7b5e1f4ec7 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -96,6 +96,7 @@ namespace osu.Game.Tests.Visual.Beatmaps var longName = CreateAPIBeatmapSet(Ruleset.Value); longName.Title = longName.TitleUnicode = "this track has an incredibly and implausibly long title"; longName.Artist = longName.ArtistUnicode = "and this artist! who would have thunk it. it's really such a long name."; + longName.Source = "wow. even the source field has an impossibly long string in it. this really takes the cake, doesn't it?"; longName.HasExplicitContent = true; longName.TrackId = 444; @@ -251,13 +252,19 @@ namespace osu.Game.Tests.Visual.Beatmaps [Test] public void TestNormal() { - createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); + createTestCase(beatmapSetInfo => new BeatmapCardNormal(beatmapSetInfo)); + } + + [Test] + public void TestExtra() + { + createTestCase(beatmapSetInfo => new BeatmapCardExtra(beatmapSetInfo)); } [Test] public void TestHoverState() { - AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, s => new BeatmapCard(s))); + AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, s => new BeatmapCardNormal(s))); AddStep("Hover card", () => InputManager.MoveMouseTo(firstCard())); AddWaitStep("wait for potential state change", 5); @@ -274,10 +281,10 @@ namespace osu.Game.Tests.Visual.Beatmaps AddWaitStep("wait for potential state change", 5); AddAssert("card is still expanded", () => firstCard().Expanded.Value); - AddStep("Hover away", () => InputManager.MoveMouseTo(this.ChildrenOfType().Last())); + AddStep("Hover away", () => InputManager.MoveMouseTo(this.ChildrenOfType().Last())); AddUntilStep("card is not expanded", () => !firstCard().Expanded.Value); - BeatmapCard firstCard() => this.ChildrenOfType().First(); + BeatmapCardNormal firstCard() => this.ChildrenOfType().First(); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 357db16e2c..88c54eb2bb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -5,7 +5,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -20,7 +19,6 @@ using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.Play; using osu.Game.Tests.Resources; -using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { @@ -31,17 +29,16 @@ namespace osu.Game.Tests.Visual.Multiplayer protected BeatmapInfo InitialBeatmap { get; private set; } protected BeatmapInfo OtherBeatmap { get; private set; } - protected IScreen CurrentScreen => multiplayerScreenStack.CurrentScreen; - protected IScreen CurrentSubScreen => multiplayerScreenStack.MultiplayerScreen.CurrentSubScreen; + protected IScreen CurrentScreen => multiplayerComponents.CurrentScreen; + protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen; private BeatmapManager beatmaps; private RulesetStore rulesets; private BeatmapSetInfo importedSet; - private TestMultiplayerScreenStack multiplayerScreenStack; + private TestMultiplayerComponents multiplayerComponents; - protected TestMultiplayerClient Client => multiplayerScreenStack.Client; - protected TestMultiplayerRoomManager RoomManager => multiplayerScreenStack.RoomManager; + protected TestMultiplayerClient Client => multiplayerComponents.Client; [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestUserLookupCache(); @@ -65,12 +62,12 @@ namespace osu.Game.Tests.Visual.Multiplayer OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0); }); - AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack())); - AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded); + AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents())); + AddUntilStep("wait for multiplayer to load", () => multiplayerComponents.IsLoaded); AddUntilStep("wait for lounge to load", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - AddUntilStep("wait for lounge", () => multiplayerScreenStack.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); - AddStep("open room", () => multiplayerScreenStack.ChildrenOfType().Single().Open(new Room + AddUntilStep("wait for lounge", () => multiplayerComponents.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); + AddStep("open room", () => multiplayerComponents.ChildrenOfType().Single().Open(new Room { Name = { Value = "Test Room" }, QueueMode = { Value = Mode }, @@ -87,13 +84,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddWaitStep("wait for transition", 2); - AddStep("create room", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + ClickButtonWhenEnabled(); - AddUntilStep("wait for join", () => RoomManager.RoomJoined); + AddUntilStep("wait for join", () => Client.RoomJoined); } [Test] @@ -105,24 +98,13 @@ namespace osu.Game.Tests.Visual.Multiplayer protected void RunGameplay() { AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); - clickReadyButton(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); - clickReadyButton(); + ClickButtonWhenEnabled(); - AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player player && player.IsLoaded); - AddStep("exit player", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent()); - } - - private void clickReadyButton() - { - AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType