From b15a54c91428cd8a0af019f6acc17c3b0a21318b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 11:36:00 +0900 Subject: [PATCH 01/24] Use new overlay pop-in/pop-out samples --- osu.Game/Overlays/ChatOverlay.cs | 3 +++ osu.Game/Overlays/WaveOverlayContainer.cs | 1 + osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs | 3 +++ 3 files changed, 7 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 87df08ceec..a47d10c565 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -55,6 +55,9 @@ namespace osu.Game.Overlays private const float side_bar_width = 190; private const float chat_bar_height = 60; + protected override string PopInSampleName => @"UI/overlay-big-pop-in"; + protected override string PopOutSampleName => @"UI/overlay-big-pop-out"; + [Resolved] private OsuConfigManager config { get; set; } = null!; diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 153f7f5412..848d9e60b7 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -19,6 +19,7 @@ namespace osu.Game.Overlays protected override bool StartHidden => true; protected override string PopInSampleName => "UI/wave-pop-in"; + protected override string PopOutSampleName => "UI/overlay-big-pop-out"; public const float HORIZONTAL_PADDING = 50; diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index 5753c268d9..7b631ebfea 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -32,6 +32,9 @@ namespace osu.Game.Screens.Select.Options public override bool BlockScreenWideMouse => false; + protected override string PopInSampleName => "SongSelect/options-pop-in"; + protected override string PopOutSampleName => "SongSelect/options-pop-out"; + public BeatmapOptionsOverlay() { AutoSizeAxes = Axes.Y; From 04a1f6a5085e0cc076abba71068d4b8d1031c0ab Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 11:37:07 +0900 Subject: [PATCH 02/24] Add panning to certain overlay pop-in/pop-outs --- .../Containers/OsuFocusedOverlayContainer.cs | 15 +++++++++++---- osu.Game/Overlays/LoginOverlay.cs | 2 ++ osu.Game/Overlays/NotificationOverlay.cs | 2 ++ osu.Game/Overlays/SettingsPanel.cs | 1 + 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index f92cfc2306..162c4b6a59 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -24,6 +24,7 @@ namespace osu.Game.Graphics.Containers private Sample samplePopOut; protected virtual string PopInSampleName => "UI/overlay-pop-in"; protected virtual string PopOutSampleName => "UI/overlay-pop-out"; + protected virtual double PopInOutSampleBalance => 0; protected override bool BlockNonPositionalInput => true; @@ -133,15 +134,21 @@ namespace osu.Game.Graphics.Containers return; } - if (didChange) - samplePopIn?.Play(); + if (didChange && samplePopIn != null) + { + samplePopIn.Balance.Value = PopInOutSampleBalance; + samplePopIn.Play(); + } if (BlockScreenWideMouse && DimMainContent) overlayManager?.ShowBlockingOverlay(this); break; case Visibility.Hidden: - if (didChange) - samplePopOut?.Play(); + if (didChange && samplePopOut != null) + { + samplePopOut.Balance.Value = PopInOutSampleBalance; + samplePopOut.Play(); + } if (BlockScreenWideMouse) overlayManager?.HideBlockingOverlay(this); break; diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index a575253e71..8a4bda89d9 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -20,6 +20,8 @@ namespace osu.Game.Overlays private const float transition_time = 400; + protected override double PopInOutSampleBalance => OsuGameBase.SFX_STEREO_STRENGTH; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index c9d09848f8..ef181830b7 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays public LocalisableString Title => NotificationsStrings.HeaderTitle; public LocalisableString Description => NotificationsStrings.HeaderDescription; + protected override double PopInOutSampleBalance => OsuGameBase.SFX_STEREO_STRENGTH; + public const float WIDTH = 320; public const float TRANSITION_LENGTH = 600; diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index d7f39a9d8f..58c56a5514 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -56,6 +56,7 @@ namespace osu.Game.Overlays private SeekLimitedSearchTextBox searchTextBox; protected override string PopInSampleName => "UI/settings-pop-in"; + protected override double PopInOutSampleBalance => -OsuGameBase.SFX_STEREO_STRENGTH; private readonly bool showSidebar; From 3d7ba0e18cdc9c739119125243c73a205e3f4fcb Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 17:03:45 +0900 Subject: [PATCH 03/24] Add pop-in/pop-out sfx to more overlays --- .../Collections/ManageCollectionsDialog.cs | 3 +++ .../Graphics/UserInterfaceV2/OsuPopover.cs | 25 ++++++++++++++++++- .../Screens/Edit/Setup/LabelledFileChooser.cs | 3 +++ .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 ++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 31016b807b..cc0f23d030 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -23,6 +23,9 @@ namespace osu.Game.Collections private AudioFilter lowPassFilter = null!; + protected override string PopInSampleName => @"UI/overlay-big-pop-in"; + protected override string PopOutSampleName => @"UI/overlay-big-pop-out"; + public ManageCollectionsDialog() { Anchor = Anchor.Centre; diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 381193d539..2aa42d0d50 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -21,6 +23,16 @@ namespace osu.Game.Graphics.UserInterfaceV2 private const float fade_duration = 250; private const double scale_duration = 500; + private Sample? samplePopIn; + private Sample? samplePopOut; + protected virtual string PopInSampleName => "UI/overlay-pop-in"; + protected virtual string PopOutSampleName => "UI/overlay-pop-out"; + + // required due to LoadAsyncComplete() calling PopOut() during load - similar workaround to `OsuDropdownMenu` + private bool wasOpened; + + protected virtual bool PlayPopInOutSamples => true; + public OsuPopover(bool withPadding = true) { Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding(); @@ -38,9 +50,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 } [BackgroundDependencyLoader(true)] - private void load(OverlayColourProvider? colourProvider, OsuColour colours) + private void load(OverlayColourProvider? colourProvider, OsuColour colours, AudioManager audio) { Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? colours.GreySeaFoamDarker; + samplePopIn = audio.Samples.Get(PopInSampleName); + samplePopOut = audio.Samples.Get(PopOutSampleName); } protected override Drawable CreateArrow() => Empty(); @@ -49,12 +63,21 @@ namespace osu.Game.Graphics.UserInterfaceV2 { this.ScaleTo(1, scale_duration, Easing.OutElasticHalf); this.FadeIn(fade_duration, Easing.OutQuint); + + if (PlayPopInOutSamples) + { + samplePopIn?.Play(); + wasOpened = true; + } } protected override void PopOut() { this.ScaleTo(0.7f, scale_duration, Easing.OutQuint); this.FadeOut(fade_duration, Easing.OutQuint); + + if (wasOpened && PlayPopInOutSamples) + samplePopOut?.Play(); } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs index d14357e875..61f33c4bdc 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs @@ -114,6 +114,9 @@ namespace osu.Game.Screens.Edit.Setup private partial class FileChooserPopover : OsuPopover { + protected override string PopInSampleName => "UI/overlay-big-pop-in"; + protected override string PopOutSampleName => "UI/overlay-big-pop-out"; + public FileChooserPopover(string[] handledExtensions, Bindable currentFile, string? chooserPath) { Child = new Container diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 70e4b2a589..030408de84 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -191,6 +191,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge protected override bool BlockNonPositionalInput => true; + protected override bool PlayPopInOutSamples => false; + public PasswordEntryPopover(Room room) { this.room = room; From d10d7b6ea41f994c586725e127647b171634fae9 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 17:07:02 +0900 Subject: [PATCH 04/24] Change some component samples to go better with overlay pop-in samples --- osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs | 4 ++-- osu.Game/Graphics/UserInterface/ShearedToggleButton.cs | 5 +++++ osu.Game/Overlays/Mods/AddPresetButton.cs | 2 ++ .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index de4df96942..0eec04541c 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -46,8 +46,8 @@ namespace osu.Game.Graphics.UserInterface private readonly Container content; private readonly Box hover; - public OsuAnimatedButton() - : base(HoverSampleSet.Button) + public OsuAnimatedButton(HoverSampleSet sampleSet = HoverSampleSet.Button) + : base(sampleSet) { base.Content.Add(content = new Container { diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index d5e0abe9d8..07ee26749a 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs @@ -14,6 +14,8 @@ namespace osu.Game.Graphics.UserInterface private Sample? sampleOff; private Sample? sampleOn; + protected virtual bool PlayClickSampleOnly => false; + /// /// Whether this button is currently toggled to an active state. /// @@ -68,6 +70,9 @@ namespace osu.Game.Graphics.UserInterface { sampleClick?.Play(); + if (PlayClickSampleOnly) + return; + if (Active.Value) sampleOn?.Play(); else diff --git a/osu.Game/Overlays/Mods/AddPresetButton.cs b/osu.Game/Overlays/Mods/AddPresetButton.cs index 731079d1d9..9063fd54f5 100644 --- a/osu.Game/Overlays/Mods/AddPresetButton.cs +++ b/osu.Game/Overlays/Mods/AddPresetButton.cs @@ -18,6 +18,8 @@ namespace osu.Game.Overlays.Mods { public partial class AddPresetButton : ShearedToggleButton, IHasPopover { + protected override bool PlayClickSampleOnly => true; + [Resolved] private OsuColour colours { get; set; } = null!; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 59b0bd1785..e36f1e9cad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -262,6 +262,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly OsuSpriteText divisorText; public DivisorDisplay() + : base(HoverSampleSet.Default) { Anchor = Anchor.Centre; Origin = Anchor.Centre; From c811546868cb8754a32049bdfbd9bc22ab0be171 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Aug 2023 21:09:50 +0900 Subject: [PATCH 05/24] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a5a9387e36..a2afcd7a9f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From fc2fac577fcdb1d329b83e2cb2e30fc5e7ea78ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Aug 2023 17:05:25 +0900 Subject: [PATCH 06/24] Inverse and xmldoc `ShearedToggleButton` sample allowance bool --- .../UserInterface/ShearedToggleButton.cs | 20 +++++++++++-------- osu.Game/Overlays/Mods/AddPresetButton.cs | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index 07ee26749a..05ed531d02 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs @@ -14,7 +14,11 @@ namespace osu.Game.Graphics.UserInterface private Sample? sampleOff; private Sample? sampleOn; - protected virtual bool PlayClickSampleOnly => false; + /// + /// Sheared toggle buttons by default play two samples when toggled: a click and a toggle (on/off). + /// Sometimes this might be too much. Setting this to false will silence the toggle sound. + /// + protected virtual bool PlayToggleSamples => true; /// /// Whether this button is currently toggled to an active state. @@ -70,13 +74,13 @@ namespace osu.Game.Graphics.UserInterface { sampleClick?.Play(); - if (PlayClickSampleOnly) - return; - - if (Active.Value) - sampleOn?.Play(); - else - sampleOff?.Play(); + if (PlayToggleSamples) + { + if (Active.Value) + sampleOn?.Play(); + else + sampleOff?.Play(); + } } } } diff --git a/osu.Game/Overlays/Mods/AddPresetButton.cs b/osu.Game/Overlays/Mods/AddPresetButton.cs index 9063fd54f5..276afd9bec 100644 --- a/osu.Game/Overlays/Mods/AddPresetButton.cs +++ b/osu.Game/Overlays/Mods/AddPresetButton.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Mods { public partial class AddPresetButton : ShearedToggleButton, IHasPopover { - protected override bool PlayClickSampleOnly => true; + protected override bool PlayToggleSamples => false; [Resolved] private OsuColour colours { get; set; } = null!; From 75750957c7cd93a2cac7d0090d146c214239eed2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Aug 2023 17:09:13 +0900 Subject: [PATCH 07/24] Add note about why pop in samples are disabled for `PasswordEntryPopover` --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 030408de84..a45583a2ec 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -191,6 +191,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge protected override bool BlockNonPositionalInput => true; + // When a room is clicked, it already plays a click sound, which clashes pretty badly with the pop in sound. + // Dunno about this one. I'd probably remove the click sound from the panel in cases they are password protected and play these pop in / out sounds. protected override bool PlayPopInOutSamples => false; public PasswordEntryPopover(Room room) From c9f611a713916ea36f9a98eb93de4c02c11cbfc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 14:30:55 +0200 Subject: [PATCH 08/24] Enable NRT in `TestSceneObjectOrderedHitPolicy` --- .../TestSceneObjectOrderedHitPolicy.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index ee70441688..8c2d8ea84e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -380,7 +378,7 @@ namespace osu.Game.Rulesets.Osu.Tests () => judgementResults.Single(r => r.HitObject == hitObject).Type, () => Is.EqualTo(result)); } - private void addJudgementAssert(string name, Func hitObject, HitResult result) + private void addJudgementAssert(string name, Func hitObject, HitResult result) { AddAssert($"{name} judgement is {result}", () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); @@ -392,8 +390,8 @@ namespace osu.Game.Rulesets.Osu.Tests () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100)); } - private ScoreAccessibleReplayPlayer currentPlayer; - private List judgementResults; + private ScoreAccessibleReplayPlayer currentPlayer = null!; + private List judgementResults = null!; private void performTest(List hitObjects, List frames) { From ab4d47b594dd45358110db8b68a76705e1089b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 14:37:58 +0200 Subject: [PATCH 09/24] Rewrite assertions to use nunit constraints --- .../TestSceneObjectOrderedHitPolicy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 8c2d8ea84e..9ee55d95a5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -381,13 +381,13 @@ namespace osu.Game.Rulesets.Osu.Tests private void addJudgementAssert(string name, Func hitObject, HitResult result) { AddAssert($"{name} judgement is {result}", - () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); + () => judgementResults.Single(r => r.HitObject == hitObject()).Type, () => Is.EqualTo(result)); } private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", - () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100)); + () => judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, () => Is.EqualTo(offset).Within(100)); } private ScoreAccessibleReplayPlayer currentPlayer = null!; From 9fd59b807f23c27f298d146c25f737305d73ecfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 15:06:06 +0200 Subject: [PATCH 10/24] Rewrite `TestSceneObjectOrderedHitPolicy` to not rely on custom hitwindows --- .../TestSceneObjectOrderedHitPolicy.cs | 117 +++++++----------- 1 file changed, 47 insertions(+), 70 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 9ee55d95a5..1dd9116da2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Screens; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; @@ -17,6 +16,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -28,8 +28,13 @@ namespace osu.Game.Rulesets.Osu.Tests { public partial class TestSceneObjectOrderedHitPolicy : RateAdjustedBeatmapTestScene { - private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss - private const double late_miss_window = 500; // time after +500 is considered a miss + private readonly OsuHitWindows referenceHitWindows; + + public TestSceneObjectOrderedHitPolicy() + { + referenceHitWindows = new OsuHitWindows(); + referenceHitWindows.SetDifficulty(0); + } /// /// Tests clicking a future circle before the first circle's start time, while the first circle HAS NOT been judged. @@ -44,12 +49,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_first_circle, Position = positionFirstCircle }, - new TestHitCircle + new HitCircle { StartTime = time_second_circle, Position = positionSecondCircle @@ -63,7 +68,8 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Miss); addJudgementAssert(hitObjects[1], HitResult.Miss); - addJudgementOffsetAssert(hitObjects[0], late_miss_window); + // note lock prevented the object from being hit, so the judgement offset should be very late. + addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh)); } /// @@ -79,12 +85,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_first_circle, Position = positionFirstCircle }, - new TestHitCircle + new HitCircle { StartTime = time_second_circle, Position = positionSecondCircle @@ -98,7 +104,8 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Miss); addJudgementAssert(hitObjects[1], HitResult.Miss); - addJudgementOffsetAssert(hitObjects[0], late_miss_window); + // note lock prevented the object from being hit, so the judgement offset should be very late. + addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh)); } /// @@ -114,12 +121,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_first_circle, Position = positionFirstCircle }, - new TestHitCircle + new HitCircle { StartTime = time_second_circle, Position = positionSecondCircle @@ -133,7 +140,8 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Miss); addJudgementAssert(hitObjects[1], HitResult.Miss); - addJudgementOffsetAssert(hitObjects[0], late_miss_window); + // note lock prevented the object from being hit, so the judgement offset should be very late. + addJudgementOffsetAssert(hitObjects[0], referenceHitWindows.WindowFor(HitResult.Meh)); } /// @@ -149,12 +157,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_first_circle, Position = positionFirstCircle }, - new TestHitCircle + new HitCircle { StartTime = time_second_circle, Position = positionSecondCircle @@ -167,8 +175,8 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } }); - addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[0], HitResult.Meh); + addJudgementAssert(hitObjects[1], HitResult.Meh); addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100 } @@ -186,12 +194,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_first_circle, Position = positionFirstCircle }, - new TestHitCircle + new HitCircle { StartTime = time_second_circle, Position = positionSecondCircle @@ -204,8 +212,8 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Time = time_first_circle, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } }); - addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[0], HitResult.Meh); + addJudgementAssert(hitObjects[1], HitResult.Ok); addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 addJudgementOffsetAssert(hitObjects[1], -100); // time_second_circle - first_circle_time } @@ -223,19 +231,19 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_circle, Position = positionCircle }, - new TestSlider + new Slider { StartTime = time_slider, Position = positionSlider, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, - new Vector2(25, 0), + new Vector2(50, 0), }) } }; @@ -265,19 +273,19 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_circle, Position = positionCircle }, - new TestSlider + new Slider { StartTime = time_slider, Position = positionSlider, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, - new Vector2(25, 0), + new Vector2(50, 0), }) } }; @@ -285,11 +293,11 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(hitObjects, new List { new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } }, - new OsuReplayFrame { Time = time_circle + late_miss_window - 100, Position = positionCircle, Actions = { OsuAction.RightButton } }, - new OsuReplayFrame { Time = time_circle + late_miss_window - 90, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_circle + referenceHitWindows.WindowFor(HitResult.Meh) - 100, Position = positionCircle, Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_circle + referenceHitWindows.WindowFor(HitResult.Meh) - 90, Position = positionSlider, Actions = { OsuAction.LeftButton } }, }); - addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[0], HitResult.Ok); addJudgementAssert(hitObjects[1], HitResult.Great); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); @@ -302,7 +310,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestHitCircleBeforeSpinner() { const double time_spinner = 1500; - const double time_circle = 1800; + const double time_circle = 1600; Vector2 positionCircle = Vector2.Zero; var hitObjects = new List @@ -313,7 +321,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), EndTime = time_spinner + 1000, }, - new TestHitCircle + new HitCircle { StartTime = time_circle, Position = positionCircle @@ -331,7 +339,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Meh); } [Test] @@ -344,12 +352,12 @@ namespace osu.Game.Rulesets.Osu.Tests var hitObjects = new List { - new TestHitCircle + new HitCircle { StartTime = time_circle, Position = positionCircle }, - new TestSlider + new Slider { StartTime = time_slider, Position = positionSlider, @@ -400,7 +408,11 @@ namespace osu.Game.Rulesets.Osu.Tests Beatmap.Value = CreateWorkingBeatmap(new Beatmap { HitObjects = hitObjects, - Difficulty = new BeatmapDifficulty { SliderTickRate = 3 }, + Difficulty = new BeatmapDifficulty + { + OverallDifficulty = 0, + SliderTickRate = 3 + }, BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo @@ -428,28 +440,6 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } - private class TestHitCircle : HitCircle - { - protected override HitWindows CreateHitWindows() => new TestHitWindows(); - } - - private class TestSlider : Slider - { - public TestSlider() - { - SliderVelocity = 0.1f; - - DefaultsApplied += _ => - { - HeadCircle.HitWindows = new TestHitWindows(); - TailCircle.HitWindows = new TestHitWindows(); - - HeadCircle.HitWindows.SetDifficulty(0); - TailCircle.HitWindows.SetDifficulty(0); - }; - } - } - private class TestSpinner : Spinner { protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) @@ -459,19 +449,6 @@ namespace osu.Game.Rulesets.Osu.Tests } } - private class TestHitWindows : HitWindows - { - private static readonly DifficultyRange[] ranges = - { - new DifficultyRange(HitResult.Great, 500, 500, 500), - new DifficultyRange(HitResult.Miss, early_miss_window, early_miss_window, early_miss_window), - }; - - public override bool IsHitResultAllowed(HitResult result) => result == HitResult.Great || result == HitResult.Miss; - - protected override DifficultyRange[] GetRanges() => ranges; - } - private partial class ScoreAccessibleReplayPlayer : ReplayPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; From a1b4a5621592234f4787d26a88d1b03830661c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Aug 2023 18:04:20 +0200 Subject: [PATCH 11/24] Add capability to export ordered object policy test cases for stable crosscheck --- .../TestSceneObjectOrderedHitPolicy.cs | 86 ++++++++++++++++++- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 1dd9116da2..5205400a7e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -3,12 +3,17 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -20,6 +25,7 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; using osuTK; @@ -30,6 +36,13 @@ namespace osu.Game.Rulesets.Osu.Tests { private readonly OsuHitWindows referenceHitWindows; + /// + /// This is provided as a convenience for testing note lock behaviour against osu!stable. + /// Setting this field to a non-null path will cause beatmap files and replays used in all test cases + /// to be exported to disk so that they can be cross-checked against stable. + /// + private readonly string? exportLocation = null; + public TestSceneObjectOrderedHitPolicy() { referenceHitWindows = new OsuHitWindows(); @@ -401,12 +414,21 @@ namespace osu.Game.Rulesets.Osu.Tests private ScoreAccessibleReplayPlayer currentPlayer = null!; private List judgementResults = null!; - private void performTest(List hitObjects, List frames) + private void performTest(List hitObjects, List frames, [CallerMemberName] string testCaseName = "") { - AddStep("load player", () => + IBeatmap playableBeatmap = null!; + Score score = null!; + + AddStep("create beatmap", () => { + var cpi = new ControlPointInfo(); + cpi.Add(0, new TimingControlPoint { BeatLength = 1000 }); Beatmap.Value = CreateWorkingBeatmap(new Beatmap { + Metadata = + { + Title = testCaseName + }, HitObjects = hitObjects, Difficulty = new BeatmapDifficulty { @@ -417,11 +439,69 @@ namespace osu.Game.Rulesets.Osu.Tests { Ruleset = new OsuRuleset().RulesetInfo }, + ControlPointInfo = cpi + }); + playableBeatmap = Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo); + }); + + AddStep("create score", () => + { + score = new Score + { + Replay = new Replay + { + Frames = new List + { + // required for correct playback in stable + new OsuReplayFrame(0, new Vector2(256, -500)), + new OsuReplayFrame(0, new Vector2(256, -500)) + }.Concat(frames).ToList() + }, + ScoreInfo = + { + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = playableBeatmap.BeatmapInfo + } + }; + }); + + if (exportLocation != null) + { + AddStep("create beatmap", () => + { + var beatmapEncoder = new LegacyBeatmapEncoder(playableBeatmap, null); + + using (var stream = File.Open(Path.Combine(exportLocation, $"{testCaseName}.osu"), FileMode.Create)) + { + var memoryStream = new MemoryStream(); + using (var writer = new StreamWriter(memoryStream, Encoding.UTF8, leaveOpen: true)) + beatmapEncoder.Encode(writer); + + memoryStream.Seek(0, SeekOrigin.Begin); + memoryStream.CopyTo(stream); + memoryStream.Seek(0, SeekOrigin.Begin); + playableBeatmap.BeatmapInfo.MD5Hash = memoryStream.ComputeMD5Hash(); + } }); + AddStep("export score", () => + { + var scoreToEncode = score.DeepClone(); + scoreToEncode.Replay.Frames = scoreToEncode.Replay.Frames.Cast() + .Select(frame => new OsuReplayFrame(frame.Time + LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET, frame.Position, frame.Actions.ToArray())) + .ToList(); + + using var stream = File.Open(Path.Combine(exportLocation, $"{testCaseName}.osr"), FileMode.Create); + var encoder = new LegacyScoreEncoder(scoreToEncode, playableBeatmap); + encoder.Encode(stream); + }); + } + + AddStep("load player", () => + { SelectedMods.Value = new[] { new OsuModClassic() }; - var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + var p = new ScoreAccessibleReplayPlayer(score); p.OnLoadComplete += _ => { From 64786aaee8d99de8757648635cf1daa3d7ec75d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Aug 2023 10:43:48 +0200 Subject: [PATCH 12/24] Adjust test cases slightly to avoid running into hitwindow edge issue Some note lock test cases do not play out correctly when exported out to stable due to a completely separate issue, namely #11311. Adjust the test cases for now to isolate failure vectors. --- .../TestSceneObjectOrderedHitPolicy.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 5205400a7e..fd8973c375 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -184,14 +184,14 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_first_circle - 200, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, - new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } + new OsuReplayFrame { Time = time_first_circle - 190, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle - 90, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } }); addJudgementAssert(hitObjects[0], HitResult.Meh); addJudgementAssert(hitObjects[1], HitResult.Meh); - addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 - addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100 + addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190 + addJudgementOffsetAssert(hitObjects[0], -90); // time_second_circle - first_circle_time - 90 } /// @@ -221,13 +221,13 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_first_circle - 200, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle - 190, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, new OsuReplayFrame { Time = time_first_circle, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } }); addJudgementAssert(hitObjects[0], HitResult.Meh); addJudgementAssert(hitObjects[1], HitResult.Ok); - addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 + addJudgementOffsetAssert(hitObjects[0], -190); // time_first_circle - 190 addJudgementOffsetAssert(hitObjects[1], -100); // time_second_circle - first_circle_time } @@ -343,7 +343,7 @@ namespace osu.Game.Rulesets.Osu.Tests performTest(hitObjects, new List { - new OsuReplayFrame { Time = time_spinner - 100, Position = positionCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_spinner - 90, Position = positionCircle, Actions = { OsuAction.LeftButton } }, new OsuReplayFrame { Time = time_spinner + 10, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } }, new OsuReplayFrame { Time = time_spinner + 20, Position = new Vector2(256, 172), Actions = { OsuAction.RightButton } }, new OsuReplayFrame { Time = time_spinner + 30, Position = new Vector2(276, 192), Actions = { OsuAction.RightButton } }, From 91c2cadb4737839e36d087c02658b4872cd2456a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 19:13:32 +0900 Subject: [PATCH 13/24] Add missing colon in mod settings tooltip --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index f9812d6c00..6d40826afe 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mods var bindable = (IBindable)property.GetValue(this)!; if (!bindable.IsDefault) - tooltipTexts.Add($"{attr.Label} {bindable}"); + tooltipTexts.Add($"{attr.Label}: {bindable}"); } return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s))); From d6aded3ac31b232884f0642bedc793f41b377b2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 20:11:55 +0900 Subject: [PATCH 14/24] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8ebfde8047..2d15bce85a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - +