Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ctrl-up/down change speed modifier of mods #28071

Merged
merged 27 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f534c4a
Initial implementation
May 2, 2024
5c21a03
F1 also does not work with minus in song select, same behaviour
May 2, 2024
7527ddb
Comment, make code more readable, functions are now private
May 2, 2024
fa0b631
Merge branch 'master' into osu-lazer-speedkeys
Fabiano1337 May 2, 2024
e737635
Merge pull request #1 from Fabiano1337/osu-lazer-speedkeys
Fabiano1337 May 2, 2024
588badf
Fix Formatting
May 2, 2024
4b5ea6b
Fix Code Inspection
May 2, 2024
a12a20e
Change Inputkeys to Ctrl+Up/Ctrl+Down
May 18, 2024
80064c4
Speedchange now also works in Modselect
May 18, 2024
99f30d9
Add Unit Tests
May 18, 2024
3fdbd73
change to single Function,
May 18, 2024
148afd1
Change Speedchange behaviour to keep changing while holding key, Add …
May 21, 2024
3403789
Toast now only shows when speed is actually changed
May 21, 2024
99d99ce
Basic cleanup
peppy May 22, 2024
02a388c
Fix enum not being at end (and adjust naming)
peppy May 22, 2024
1fdebe9
Merge branch 'master' into lazer-speedkeys
peppy May 22, 2024
f979200
Use null conditional rather than implicit not-null
peppy May 22, 2024
57da422
Add speed value to Toast
May 22, 2024
abc67eb
Fix test not running due to floating point number inaccuacy
May 22, 2024
0df6345
Improve readability
May 22, 2024
9045ec2
Rewrite test
bdach May 24, 2024
63406b6
Rewrite implementation
bdach May 24, 2024
345fb60
Fix toast strings
bdach May 24, 2024
8cac87e
Fix speed controls in mod select overlay not handling repeat
bdach May 24, 2024
b1b2079
Actually use return value
bdach May 24, 2024
cab8cf7
Move mod speed hotkey handler to user mod select overlay
bdach May 24, 2024
c800bb5
Merge branch 'master' into lazer-speedkeys
bdach May 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,105 @@ public override void SetUpSteps()
AddStep("delete all beatmaps", () => manager.Delete());
}

[Test]
public void TestSpeedChange()
{
createSongSelect();
changeMods();

decreaseModSpeed();
AddAssert("half time activated at 0.95x", () => songSelect!.Mods.Value.OfType<ModHalfTime>().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005));

decreaseModSpeed();
AddAssert("half time speed changed to 0.9x", () => songSelect!.Mods.Value.OfType<ModHalfTime>().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005));

increaseModSpeed();
AddAssert("half time speed changed to 0.95x", () => songSelect!.Mods.Value.OfType<ModHalfTime>().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005));

increaseModSpeed();
AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0);

increaseModSpeed();
AddAssert("double time activated at 1.05x", () => songSelect!.Mods.Value.OfType<ModDoubleTime>().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005));

increaseModSpeed();
AddAssert("double time speed changed to 1.1x", () => songSelect!.Mods.Value.OfType<ModDoubleTime>().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005));

decreaseModSpeed();
AddAssert("double time speed changed to 1.05x", () => songSelect!.Mods.Value.OfType<ModDoubleTime>().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005));

OsuModNightcore nc = new OsuModNightcore
{
SpeedChange = { Value = 1.05 }
};
changeMods(nc);

increaseModSpeed();
AddAssert("nightcore speed changed to 1.1x", () => songSelect!.Mods.Value.OfType<ModNightcore>().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005));

decreaseModSpeed();
AddAssert("nightcore speed changed to 1.05x", () => songSelect!.Mods.Value.OfType<ModNightcore>().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005));

decreaseModSpeed();
AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0);

decreaseModSpeed();
AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType<ModDaycore>().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005));

decreaseModSpeed();
AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType<ModDaycore>().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005));

increaseModSpeed();
AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType<ModDaycore>().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005));

OsuModDoubleTime dt = new OsuModDoubleTime
{
SpeedChange = { Value = 1.02 },
AdjustPitch = { Value = true },
};
changeMods(dt);

decreaseModSpeed();
AddAssert("half time activated at 0.97x", () => songSelect!.Mods.Value.OfType<ModHalfTime>().Single().SpeedChange.Value, () => Is.EqualTo(0.97).Within(0.005));
AddAssert("adjust pitch preserved", () => songSelect!.Mods.Value.OfType<ModHalfTime>().Single().AdjustPitch.Value, () => Is.True);

OsuModHalfTime ht = new OsuModHalfTime
{
SpeedChange = { Value = 0.97 },
AdjustPitch = { Value = true },
};
Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() };
changeMods(modlist);

increaseModSpeed();
AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType<ModDoubleTime>().Single().SpeedChange.Value, () => Is.EqualTo(1.02).Within(0.005));
AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType<ModDoubleTime>().Single().AdjustPitch.Value, () => Is.True);
AddAssert("HD still enabled", () => songSelect!.Mods.Value.OfType<ModHidden>().SingleOrDefault(), () => Is.Not.Null);
AddAssert("HR still enabled", () => songSelect!.Mods.Value.OfType<ModHardRock>().SingleOrDefault(), () => Is.Not.Null);

changeMods(new ModWindUp());
increaseModSpeed();
AddAssert("windup still active", () => songSelect!.Mods.Value.First() is ModWindUp);

changeMods(new ModAdaptiveSpeed());
increaseModSpeed();
AddAssert("adaptive speed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed);

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);
});
}

[Test]
public void TestPlaceholderBeatmapPresence()
{
Expand Down
8 changes: 8 additions & 0 deletions osu.Game/Input/Bindings/GlobalActionContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ public static IEnumerable<GlobalAction> GetGlobalActionsFor(GlobalActionCategory
new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom),
new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions),
new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods),
new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseModSpeed),
new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseModSpeed),
};

private static IEnumerable<KeyBinding> audioControlKeyBindings => new[]
Expand Down Expand Up @@ -420,6 +422,12 @@ public enum GlobalAction

[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayBackward))]
StepReplayBackward,

[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseModSpeed))]
IncreaseModSpeed,

[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseModSpeed))]
DecreaseModSpeed,
}

public enum GlobalActionCategory
Expand Down
10 changes: 10 additions & 0 deletions osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,16 @@ public static class GlobalActionKeyBindingStrings
/// </summary>
public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control");

/// <summary>
/// "Increase mod speed"
/// </summary>
public static LocalisableString IncreaseModSpeed => new TranslatableString(getKey(@"increase_mod_speed"), @"Increase mod speed");

/// <summary>
/// "Decrease mod speed"
/// </summary>
public static LocalisableString DecreaseModSpeed => new TranslatableString(getKey(@"decrease_mod_speed"), @"Decrease mod speed");

private static string getKey(string key) => $@"{prefix}:{key}";
}
}
5 changes: 5 additions & 0 deletions osu.Game/Localisation/ToastStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public static class ToastStrings
/// </summary>
public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied");

/// <summary>
/// "Speed changed to {0:N2}x"
/// </summary>
public static LocalisableString SpeedChangedTo(double speed) => new TranslatableString(getKey(@"speed_changed"), @"Speed changed to {0:N2}x", speed);

private static string getKey(string key) => $@"{prefix}:{key}";
}
}
2 changes: 1 addition & 1 deletion osu.Game/Overlays/Mods/ModSelectOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ private void load(OsuGameBase game, OsuColour colours, AudioManager audio, OsuCo
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Height = 0
}
},
});

MainAreaContent.AddRange(new Drawable[]
Expand Down
26 changes: 26 additions & 0 deletions osu.Game/Overlays/Mods/UserModSelectOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,30 @@

using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select;
using osu.Game.Utils;

namespace osu.Game.Overlays.Mods
{
public partial class UserModSelectOverlay : ModSelectOverlay
{
private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!;

public UserModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
: base(colourScheme)
{
}

[BackgroundDependencyLoader]
private void load()
{
Add(modSpeedHotkeyHandler = new ModSpeedHotkeyHandler());
}

protected override ModColumn CreateModColumn(ModType modType) => new UserModColumn(modType, false);

protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection)
Expand All @@ -38,6 +50,20 @@ protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<
return modsAfterRemoval.ToList();
}

public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
switch (e.Action)
{
case GlobalAction.IncreaseModSpeed:
return modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod));

case GlobalAction.DecreaseModSpeed:
return modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod));
}

return base.OnPressed(e);
}

private partial class UserModColumn : ModColumn
{
public UserModColumn(ModType modType, bool allowIncompatibleSelection)
Expand Down
17 changes: 17 additions & 0 deletions osu.Game/Overlays/OSD/SpeedChangeToast.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;

namespace osu.Game.Overlays.OSD
{
public partial class SpeedChangeToast : Toast
{
public SpeedChangeToast(OsuConfigManager config, double newSpeed)
: base(ModSelectOverlayStrings.ModCustomisation, ToastStrings.SpeedChangedTo(newSpeed), config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed))
{
}
}
}
105 changes: 105 additions & 0 deletions osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Overlays;
using osu.Game.Overlays.OSD;
using osu.Game.Rulesets.Mods;
using osu.Game.Utils;

namespace osu.Game.Screens.Select
{
public partial class ModSpeedHotkeyHandler : Component
{
[Resolved]
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } = null!;

[Resolved]
private OsuConfigManager config { get; set; } = null!;

[Resolved]
private OnScreenDisplay? onScreenDisplay { get; set; }

private ModRateAdjust? lastActiveRateAdjustMod;

protected override void LoadComplete()
{
base.LoadComplete();

selectedMods.BindValueChanged(val =>
{
lastActiveRateAdjustMod = val.NewValue.OfType<ModRateAdjust>().SingleOrDefault() ?? lastActiveRateAdjustMod;
}, true);
}

public bool ChangeSpeed(double delta, IEnumerable<Mod> availableMods)
{
double targetSpeed = (selectedMods.Value.OfType<ModRateAdjust>().SingleOrDefault()?.SpeedChange.Value ?? 1) + delta;

if (Precision.AlmostEquals(targetSpeed, 1, 0.005))
{
selectedMods.Value = selectedMods.Value.Where(m => m is not ModRateAdjust).ToList();
onScreenDisplay?.Display(new SpeedChangeToast(config, targetSpeed));
return true;
}

ModRateAdjust? targetMod;

if (lastActiveRateAdjustMod is ModDaycore || lastActiveRateAdjustMod is ModNightcore)
{
targetMod = targetSpeed < 1
? availableMods.OfType<ModDaycore>().SingleOrDefault()
: availableMods.OfType<ModNightcore>().SingleOrDefault();
}
else
{
targetMod = targetSpeed < 1
? availableMods.OfType<ModHalfTime>().SingleOrDefault()
: availableMods.OfType<ModDoubleTime>().SingleOrDefault();
}

if (targetMod == null)
return false;

// preserve other settings from latest rate adjust mod instance seen
if (lastActiveRateAdjustMod != null)
{
foreach (var (_, sourceProperty) in lastActiveRateAdjustMod.GetSettingsSourceProperties())
{
if (sourceProperty.Name == nameof(ModRateAdjust.SpeedChange))
continue;

var targetProperty = targetMod.GetType().GetProperty(sourceProperty.Name);

if (targetProperty == null)
continue;

var targetBindable = (IBindable)targetProperty.GetValue(targetMod)!;
var sourceBindable = (IBindable)sourceProperty.GetValue(lastActiveRateAdjustMod)!;

if (targetBindable.GetType() != sourceBindable.GetType())
continue;

lastActiveRateAdjustMod.CopyAdjustedSetting(targetBindable, sourceBindable);
}
}

targetMod.SpeedChange.Value = targetSpeed;

var intendedMods = selectedMods.Value.Where(m => m is not ModRateAdjust).Append(targetMod).ToList();

if (!ModUtils.CheckCompatibleSet(intendedMods))
return false;

selectedMods.Value = intendedMods;
onScreenDisplay?.Display(new SpeedChangeToast(config, targetMod.SpeedChange.Value));
return true;
}
}
}
19 changes: 17 additions & 2 deletions osu.Game/Screens/Select/SongSelect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
using osu.Game.Screens.Select.Details;
using osu.Game.Screens.Select.Options;
using osu.Game.Skinning;
using osu.Game.Utils;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
Expand Down Expand Up @@ -98,6 +99,9 @@ public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBind
new OsuMenuItem(@"Select", MenuItemType.Highlighted, () => FinaliseSelection(getBeatmap()))
};

[Resolved]
private OsuGameBase game { get; set; } = null!;

[Resolved]
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } = null!;

Expand Down Expand Up @@ -133,6 +137,7 @@ public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBind
private double audioFeedbackLastPlaybackTime;

private IDisposable? modSelectOverlayRegistration;
private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!;

private AdvancedStats advancedStats = null!;

Expand Down Expand Up @@ -319,6 +324,7 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog
{
RelativeSizeAxes = Axes.Both,
},
modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(),
});

if (ShowFooter)
Expand Down Expand Up @@ -1007,11 +1013,20 @@ public void ClearScores(BeatmapInfo? beatmapInfo)

public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> 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)));
}

if (e.Repeat)
return false;

if (!this.IsCurrentScreen()) return false;

switch (e.Action)
{
case GlobalAction.Select:
Expand Down
Loading