mirror of
https://github.com/ppy/osu.git
synced 2026-05-18 23:50:42 +08:00
1885ab86f5
Closes https://github.com/ppy/osu/issues/36490. While I'm out here already taking heat for deleting mod combinations let me do more of that. The main problem with the mod combination here, again, is that the application of the mods does not commute. - The current behaviour is that TP is applied first, then DA. This is, again, "enforced" by the mod select overlay implicitly enforcing order of the mod instances in the global mod bindable to match the display order of mods inside it. Even this doesn't "work" correctly as is, because as the bug reporter points out, if they throw on DA with no changes expecting the map's default AR to be applied, it still gets halved. This is because DA works in a way wherein if you don't touch the AR slider, DA does not touch AR. Which means that the DA slider should *really* be at *half* of the map's base AR by default in this case because TP is active. How do you program this? - The *alternative* behaviour would be that DA is applied first, then TP. This in turn would mean that the effective range of AR adjustment offered by DA when TP is active would be halved to [0, 5] (or [-5, 5.5] with extended ranges). How do you program this? The above is just client-side concerns, while leaving out the other giant concern, which is "how do you get every single place in the game that may want to apply mods to a beatmap to apply them *in the same order*?". Then extend that to server-side components, then extend that to every external library that may want to re-implement SR/PP calculations, etc. etc. One additional remark: What the bug reporter *did not* say however, but I am saying, is that there's an elephant in the room, and that is the Easy mod, which *also* changes AR, but also *happens* to apply commutatively with Target Practice simply because both mods are implemented to halve the AR, which means that the order of application doesn't matter. If I were *really* bent on being a bad guy and just deleting mod combinations indiscriminately, I'd delete that one as well. But I'm not doing that.
110 lines
4.3 KiB
C#
110 lines
4.3 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;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Localisation;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Beatmaps.ControlPoints;
|
|
using osu.Game.Configuration;
|
|
using osu.Game.Extensions;
|
|
using osu.Game.Graphics.UserInterface;
|
|
using osu.Game.Rulesets.Mods;
|
|
using osu.Game.Rulesets.Osu.Objects;
|
|
|
|
namespace osu.Game.Rulesets.Osu.Mods
|
|
{
|
|
public partial class OsuModDifficultyAdjust : ModDifficultyAdjust
|
|
{
|
|
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
|
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
|
{
|
|
Precision = 0.1f,
|
|
MinValue = 0,
|
|
MaxValue = 10,
|
|
ExtendedMaxValue = 11,
|
|
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
|
};
|
|
|
|
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(ApproachRateSettingsControl))]
|
|
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
|
{
|
|
Precision = 0.1f,
|
|
MinValue = 0,
|
|
MaxValue = 10,
|
|
ExtendedMinValue = -10,
|
|
ExtendedMaxValue = 11,
|
|
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
|
};
|
|
|
|
public override string ExtendedIconInformation
|
|
{
|
|
get
|
|
{
|
|
if (!IsExactlyOneSettingChanged(CircleSize, ApproachRate, OverallDifficulty, DrainRate))
|
|
return string.Empty;
|
|
|
|
if (!CircleSize.IsDefault) return format("CS", CircleSize);
|
|
if (!ApproachRate.IsDefault) return format("AR", ApproachRate);
|
|
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty);
|
|
if (!DrainRate.IsDefault) return format("HP", DrainRate);
|
|
|
|
return string.Empty;
|
|
|
|
string format(string acronym, DifficultyBindable bindable)
|
|
=> $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
|
}
|
|
}
|
|
|
|
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
|
{
|
|
get
|
|
{
|
|
if (!CircleSize.IsDefault)
|
|
yield return ("Circle size", $"{CircleSize.Value:N1}");
|
|
|
|
foreach (var setting in base.SettingDescription)
|
|
yield return setting;
|
|
|
|
if (!ApproachRate.IsDefault)
|
|
yield return ("Approach rate", $"{ApproachRate.Value:N1}");
|
|
}
|
|
}
|
|
|
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTargetPractice)).ToArray();
|
|
|
|
protected override void ApplySettings(BeatmapDifficulty difficulty)
|
|
{
|
|
base.ApplySettings(difficulty);
|
|
|
|
if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value;
|
|
if (ApproachRate.Value != null) difficulty.ApproachRate = ApproachRate.Value.Value;
|
|
}
|
|
|
|
private partial class ApproachRateSettingsControl : DifficultyAdjustSettingsControl
|
|
{
|
|
protected override RoundedSliderBar<float> CreateSlider(BindableNumber<float> current) => new ApproachRateSlider();
|
|
|
|
/// <summary>
|
|
/// A slider bar with more detailed approach rate info for its given value
|
|
/// </summary>
|
|
public partial class ApproachRateSlider : RoundedSliderBar<float>
|
|
{
|
|
public override LocalisableString TooltipText =>
|
|
(Current as BindableNumber<float>)?.MinValue < 0
|
|
? $"{base.TooltipText} ({getPreemptTime(Current.Value):0} ms)"
|
|
: base.TooltipText;
|
|
|
|
private double getPreemptTime(float approachRate)
|
|
{
|
|
var hitCircle = new HitCircle();
|
|
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { ApproachRate = approachRate });
|
|
return hitCircle.TimePreempt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|