1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 09:27:29 +08:00

Merge pull request #28071 from Fabiano1337/lazer-speedkeys

Make ctrl-up/down change speed modifier of mods
This commit is contained in:
Dean Herbert 2024-05-24 23:20:04 +09:00 committed by GitHub
commit 2134ff7a1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 288 additions and 3 deletions

View File

@ -87,6 +87,105 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("delete all beatmaps", () => manager.Delete()); 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] [Test]
public void TestPlaceholderBeatmapPresence() public void TestPlaceholderBeatmapPresence()
{ {

View File

@ -182,6 +182,8 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom),
new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions), new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions),
new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods), 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[] private static IEnumerable<KeyBinding> audioControlKeyBindings => new[]
@ -420,6 +422,12 @@ namespace osu.Game.Input.Bindings
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayBackward))] [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayBackward))]
StepReplayBackward, StepReplayBackward,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseModSpeed))]
IncreaseModSpeed,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseModSpeed))]
DecreaseModSpeed,
} }
public enum GlobalActionCategory public enum GlobalActionCategory

View File

@ -369,6 +369,16 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control"); 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}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -49,6 +49,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied"); 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}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Mods
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Height = 0 Height = 0
} },
}); });
MainAreaContent.AddRange(new Drawable[] MainAreaContent.AddRange(new Drawable[]

View File

@ -3,18 +3,30 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; 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.Rulesets.Mods;
using osu.Game.Screens.Select;
using osu.Game.Utils; using osu.Game.Utils;
namespace osu.Game.Overlays.Mods namespace osu.Game.Overlays.Mods
{ {
public partial class UserModSelectOverlay : ModSelectOverlay public partial class UserModSelectOverlay : ModSelectOverlay
{ {
private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!;
public UserModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) public UserModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
: base(colourScheme) : base(colourScheme)
{ {
} }
[BackgroundDependencyLoader]
private void load()
{
Add(modSpeedHotkeyHandler = new ModSpeedHotkeyHandler());
}
protected override ModColumn CreateModColumn(ModType modType) => new UserModColumn(modType, false); protected override ModColumn CreateModColumn(ModType modType) => new UserModColumn(modType, false);
protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection)
@ -38,6 +50,20 @@ namespace osu.Game.Overlays.Mods
return modsAfterRemoval.ToList(); 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 private partial class UserModColumn : ModColumn
{ {
public UserModColumn(ModType modType, bool allowIncompatibleSelection) public UserModColumn(ModType modType, bool allowIncompatibleSelection)

View File

@ -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))
{
}
}
}

View File

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

View File

@ -39,6 +39,7 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Details;
using osu.Game.Screens.Select.Options; using osu.Game.Screens.Select.Options;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Utils;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
@ -98,6 +99,9 @@ namespace osu.Game.Screens.Select
new OsuMenuItem(@"Select", MenuItemType.Highlighted, () => FinaliseSelection(getBeatmap())) new OsuMenuItem(@"Select", MenuItemType.Highlighted, () => FinaliseSelection(getBeatmap()))
}; };
[Resolved]
private OsuGameBase game { get; set; } = null!;
[Resolved] [Resolved]
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } = null!; private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } = null!;
@ -133,6 +137,7 @@ namespace osu.Game.Screens.Select
private double audioFeedbackLastPlaybackTime; private double audioFeedbackLastPlaybackTime;
private IDisposable? modSelectOverlayRegistration; private IDisposable? modSelectOverlayRegistration;
private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!;
private AdvancedStats advancedStats = null!; private AdvancedStats advancedStats = null!;
@ -319,6 +324,7 @@ namespace osu.Game.Screens.Select
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(),
}); });
if (ShowFooter) if (ShowFooter)
@ -1007,11 +1013,20 @@ namespace osu.Game.Screens.Select
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e) 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) if (e.Repeat)
return false; return false;
if (!this.IsCurrentScreen()) return false;
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.Select: case GlobalAction.Select: