diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index d9d740c145..59899637f9 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -249,6 +249,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (action != Action.Value) return false; + // do not run any of this logic when rewinding, as it inverts order of presses/releases. + if (Time.Elapsed < 0) + return false; + if (CheckHittable?.Invoke(this, Time.Current) == false) return false; @@ -281,6 +285,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (action != Action.Value) return; + // do not run any of this logic when rewinding, as it inverts order of presses/releases. + if (Time.Elapsed < 0) + return; + // Make sure a hold was started if (HoldStartTime == null) return; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index fd7ea050b9..24bf79f9ae 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -45,12 +45,12 @@ namespace osu.Game.Rulesets.Osu.Edit public override bool HandleReverse() { - var hitObjects = selectedMovableObjects; + var hitObjects = EditorBeatmap.SelectedHitObjects; double endTime = hitObjects.Max(h => h.GetEndTime()); double startTime = hitObjects.Min(h => h.StartTime); - bool moreThanOneObject = hitObjects.Length > 1; + bool moreThanOneObject = hitObjects.Count > 1; foreach (var h in hitObjects) { diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index a4190e0b84..21d3bdaae3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Screens; using osu.Game.Screens.Menu; @@ -73,6 +76,22 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("did perform", () => actionPerformed); } + [Test] + public void TestOverlaysAlwaysClosed() + { + ChatOverlay chat = null; + AddUntilStep("is at menu", () => Game.ScreenStack.CurrentScreen is MainMenu); + AddUntilStep("wait for chat load", () => (chat = Game.ChildrenOfType().SingleOrDefault()) != null); + + AddStep("show chat", () => InputManager.Key(Key.F8)); + + AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); + + AddUntilStep("still at menu", () => Game.ScreenStack.CurrentScreen is MainMenu); + AddAssert("did perform", () => actionPerformed); + AddAssert("chat closed", () => chat.State.Value == Visibility.Hidden); + } + [TestCase(true)] [TestCase(false)] public void TestPerformBlockedByDialog(bool confirmed) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 9cb02ff3b9..be464fa2b7 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -68,6 +68,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Positional hitsounds", Current = config.GetBindable(OsuSetting.PositionalHitSounds) }, + new SettingsCheckbox + { + LabelText = "Always play first combo break sound", + Current = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak) + }, new SettingsEnumDropdown { LabelText = "Score meter type", diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index 9afe87f74f..5898c116dd 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -76,6 +76,8 @@ namespace osu.Game // a dialog may be blocking the execution for now. if (checkForDialog(current)) return; + game.CloseAllOverlays(false); + // we may already be at the target screen type. if (validScreens.Contains(getCurrentScreen().GetType()) && !beatmap.Disabled) { @@ -83,8 +85,6 @@ namespace osu.Game return; } - game.CloseAllOverlays(false); - while (current != null) { if (validScreens.Contains(current.GetType())) diff --git a/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs b/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs index d8240d892f..315926dfc6 100644 --- a/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs +++ b/osu.Game/Rulesets/UI/IPooledHitObjectProvider.cs @@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.UI /// The to retrieve the representation of. /// The representing , or null if no poolable representation exists. [CanBeNull] - public DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject); + DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject); } } diff --git a/osu.Game/Screens/Play/ComboEffects.cs b/osu.Game/Screens/Play/ComboEffects.cs index 831b2f593c..5041d07e5d 100644 --- a/osu.Game/Screens/Play/ComboEffects.cs +++ b/osu.Game/Screens/Play/ComboEffects.cs @@ -17,8 +17,9 @@ namespace osu.Game.Screens.Play private SkinnableSound comboBreakSample; - private Bindable alwaysPlay; - private bool firstTime = true; + private Bindable alwaysPlayFirst; + + private double? firstBreakTime; public ComboEffects(ScoreProcessor processor) { @@ -29,7 +30,7 @@ namespace osu.Game.Screens.Play private void load(OsuConfigManager config) { InternalChild = comboBreakSample = new SkinnableSound(new SampleInfo("Gameplay/combobreak")); - alwaysPlay = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak); + alwaysPlayFirst = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak); } protected override void LoadComplete() @@ -38,12 +39,31 @@ namespace osu.Game.Screens.Play processor.Combo.BindValueChanged(onComboChange); } + [Resolved(canBeNull: true)] + private ISamplePlaybackDisabler samplePlaybackDisabler { get; set; } + + [Resolved] + private GameplayClock gameplayClock { get; set; } + private void onComboChange(ValueChangedEvent combo) { - if (combo.NewValue == 0 && (combo.OldValue > 20 || (alwaysPlay.Value && firstTime))) + // handle the case of rewinding before the first combo break time. + if (gameplayClock.CurrentTime < firstBreakTime) + firstBreakTime = null; + + if (gameplayClock.ElapsedFrameTime < 0) + return; + + if (combo.NewValue == 0 && (combo.OldValue > 20 || (alwaysPlayFirst.Value && firstBreakTime == null))) { + firstBreakTime = gameplayClock.CurrentTime; + + // combo break isn't a pausable sound itself as we want to let it play out. + // we still need to disable during seeks, though. + if (samplePlaybackDisabler?.SamplePlaybackDisabled.Value == true) + return; + comboBreakSample?.Play(); - firstTime = false; } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ee4f835c6f..b94f0a5062 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -302,12 +302,12 @@ namespace osu.Game.Screens.Play { ScoreProcessor, HealthProcessor, + new ComboEffects(ScoreProcessor), breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor) { Breaks = working.Beatmap.Breaks } }), - new ComboEffects(ScoreProcessor) } };