From 1b2054c49d39f9300236bad62c607b9e29207d81 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 12 May 2025 15:57:52 +0300 Subject: [PATCH] Add rate adjustment keybinds Add test coverage --- .../SongSelectV2/TestSceneSongSelect.cs | 122 ++++++++++++++++++ osu.Game/Screens/SelectV2/SongSelect.cs | 32 ++++- 2 files changed, 153 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs index aa1215a62c..0939f8f3f3 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs @@ -50,6 +50,128 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddAssert("beatmap set deleted", () => Beatmaps.GetAllUsableBeatmapSets().Any(), () => Is.False); } + [Test] + public void TestSpeedChange() + { + LoadSongSelect(); + AddStep("clear mods", () => SelectedMods.Value = Array.Empty()); + + decreaseModSpeed(); + AddAssert("half time activated at 0.95x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); + + decreaseModSpeed(); + AddAssert("half time speed changed to 0.9x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); + + increaseModSpeed(); + AddAssert("half time speed changed to 0.95x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); + + increaseModSpeed(); + AddAssert("no mods selected", () => SelectedMods.Value.Count == 0); + + increaseModSpeed(); + AddAssert("double time activated at 1.05x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); + + increaseModSpeed(); + AddAssert("double time speed changed to 1.1x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); + + decreaseModSpeed(); + AddAssert("double time speed changed to 1.05x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); + + OsuModNightcore nc = new OsuModNightcore + { + SpeedChange = { Value = 1.05 } + }; + AddStep("select NC", () => SelectedMods.Value = new[] { nc }); + + increaseModSpeed(); + AddAssert("nightcore speed changed to 1.1x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); + + decreaseModSpeed(); + AddAssert("nightcore speed changed to 1.05x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); + + decreaseModSpeed(); + AddAssert("no mods selected", () => SelectedMods.Value.Count == 0); + + decreaseModSpeed(); + AddAssert("daycore activated at 0.95x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); + + decreaseModSpeed(); + AddAssert("daycore activated at 0.95x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); + + increaseModSpeed(); + AddAssert("daycore activated at 0.95x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); + + OsuModDoubleTime dt = new OsuModDoubleTime + { + SpeedChange = { Value = 1.02 }, + AdjustPitch = { Value = true }, + }; + AddStep("select DT", () => SelectedMods.Value = new[] { dt }); + + decreaseModSpeed(); + AddAssert("half time activated at 0.97x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.97).Within(0.005)); + AddAssert("adjust pitch preserved", () => SelectedMods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); + + OsuModHalfTime ht = new OsuModHalfTime + { + SpeedChange = { Value = 0.97 }, + AdjustPitch = { Value = true }, + }; + Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() }; + AddStep("select HT+HD", () => SelectedMods.Value = modlist); + + increaseModSpeed(); + AddAssert("double time activated at 1.02x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.02).Within(0.005)); + AddAssert("double time activated at 1.02x", () => SelectedMods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); + AddAssert("HD still enabled", () => SelectedMods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); + AddAssert("HR still enabled", () => SelectedMods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); + + AddStep("select WU", () => SelectedMods.Value = new[] { new ModWindUp() }); + increaseModSpeed(); + AddAssert("windup still active", () => SelectedMods.Value.First() is ModWindUp); + + AddStep("select AS", () => SelectedMods.Value = new[] { new ModAdaptiveSpeed() }); + increaseModSpeed(); + AddAssert("adaptive speed still active", () => SelectedMods.Value.First() is ModAdaptiveSpeed); + + OsuModDoubleTime dtWithAdjustPitch = new OsuModDoubleTime + { + SpeedChange = { Value = 1.05 }, + AdjustPitch = { Value = true }, + }; + AddStep("select DT x1.05", () => SelectedMods.Value = new[] { dtWithAdjustPitch }); + + decreaseModSpeed(); + AddAssert("no mods selected", () => SelectedMods.Value.Count == 0); + + decreaseModSpeed(); + AddAssert("half time activated at 0.95x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); + AddAssert("half time has adjust pitch active", () => SelectedMods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); + + AddStep("turn off adjust pitch", () => SelectedMods.Value.OfType().Single().AdjustPitch.Value = false); + + increaseModSpeed(); + AddAssert("no mods selected", () => SelectedMods.Value.Count == 0); + + increaseModSpeed(); + AddAssert("double time activated at 1.05x", () => SelectedMods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); + AddAssert("double time has adjust pitch inactive", () => SelectedMods.Value.OfType().Single().AdjustPitch.Value, () => Is.False); + + void increaseModSpeed() => AddStep("increase mod speed", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Up); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + void decreaseModSpeed() => AddStep("decrease mod speed", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Down); + InputManager.ReleaseKey(Key.ControlLeft); + }); + } + #endregion #region Footer diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 2891d4621c..146d971f8f 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -9,17 +10,20 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -30,7 +34,7 @@ namespace osu.Game.Screens.SelectV2 /// This screen is intended to house all components introduced in the new song select design to add transitions and examine the overall look. /// This will be gradually built upon and ultimately replace once everything is in place. /// - public abstract partial class SongSelect : OsuScreen + public abstract partial class SongSelect : OsuScreen, IKeyBindingHandler { private const float logo_scale = 0.4f; private const double fade_duration = 300; @@ -44,6 +48,8 @@ namespace osu.Game.Screens.SelectV2 ShowPresets = true, }; + private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!; + [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); @@ -58,6 +64,9 @@ namespace osu.Game.Screens.SelectV2 public override bool ShowFooter => true; + [Resolved] + private OsuGameBase game { get; set; } = null!; + [Resolved] private OsuLogo? logo { get; set; } @@ -159,6 +168,7 @@ namespace osu.Game.Screens.SelectV2 { RelativeSizeAxes = Axes.Both, }, + modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(), modSelectOverlay, }); } @@ -342,6 +352,26 @@ namespace osu.Game.Screens.SelectV2 #region Hotkeys + public virtual bool OnPressed(KeyBindingPressEvent e) + { + if (!this.IsCurrentScreen()) return false; + + switch (e.Action) + { + case GlobalAction.IncreaseModSpeed: + return modSpeedHotkeyHandler.ChangeSpeed(0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); + + case GlobalAction.DecreaseModSpeed: + return modSpeedHotkeyHandler.ChangeSpeed(-0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + protected override bool OnKeyDown(KeyDownEvent e) { if (e.Repeat) return false;