1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-16 06:27:27 +08:00
osu-lazer/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs
Bartłomiej Dach 5e5bb49cd8
Fix rate change hotkeys sometimes losing track of adjust pitch setting
Fixes https://osu.ppy.sh/community/forums/topics/1983327.

The cause of the bug is a bit convoluted, and stems from the fact that
the mod select overlay controls all of the game-global mod instances if
present. `ModSpeedHotkeyHandler` would store the last spotted instance
of a rate adjust mod - which in this case is a problem, because on
deselection of a mod, the mod select overlay resets its settings to
defaults:

	a258059d43/osu.Game/Overlays/Mods/ModSelectOverlay.cs (L424-L425)

A way to defend against this is a clone, but this reveals another issue,
in that the existing code was *relying* on the reference to the mod
remaining the same in any other case, to read the latest valid settings
of the mod. This basically only mattered in the edge case wherein Double
Time would swap places with Half Time and vice versa (think [0.95,1.05]
range). Therefore, track mod settings too explicitly to ensure that the
stored clone is as up-to-date as possible.
2024-09-30 08:47:02 +02:00

116 lines
4.2 KiB
C#

// 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;
private ModSettingChangeTracker? settingChangeTracker;
protected override void LoadComplete()
{
base.LoadComplete();
selectedMods.BindValueChanged(val =>
{
storeLastActiveRateAdjustMod();
settingChangeTracker?.Dispose();
settingChangeTracker = new ModSettingChangeTracker(val.NewValue);
settingChangeTracker.SettingChanged += _ => storeLastActiveRateAdjustMod();
}, true);
}
private void storeLastActiveRateAdjustMod()
{
lastActiveRateAdjustMod = (ModRateAdjust?)selectedMods.Value.OfType<ModRateAdjust>().SingleOrDefault()?.DeepClone() ?? lastActiveRateAdjustMod;
}
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;
}
}
}