mirror of
https://github.com/ppy/osu.git
synced 2026-05-17 19:44:22 +08:00
5e5bb49cd8
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: https://github.com/ppy/osu/blob/a258059d4338b999b8e065e48b952d14a6d14fb8/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.
116 lines
4.2 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|