1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 07:42:57 +08:00

Merge pull request #11133 from smoogipoo/difficulty-adjustment-extension

Allow ModDifficultyAdjustment to extend beyond the sane limits of the game
This commit is contained in:
Dean Herbert 2021-02-11 16:19:00 +09:00 committed by GitHub
commit 6e2994f98e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 137 additions and 9 deletions

View File

@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public class CatchModDifficultyAdjust : ModDifficultyAdjust
{
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
public BindableNumber<float> CircleSize { get; } = new BindableFloat
public BindableNumber<float> CircleSize { get; } = new BindableFloatWithLimitExtension
{
Precision = 0.1f,
MinValue = 1,
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Mods
};
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
public BindableNumber<float> ApproachRate { get; } = new BindableFloat
public BindableNumber<float> ApproachRate { get; } = new BindableFloatWithLimitExtension
{
Precision = 0.1f,
MinValue = 1,
@ -31,6 +31,14 @@ namespace osu.Game.Rulesets.Catch.Mods
Value = 5,
};
protected override void ApplyLimits(bool extended)
{
base.ApplyLimits(extended);
CircleSize.MaxValue = extended ? 11 : 10;
ApproachRate.MaxValue = extended ? 11 : 10;
}
public override string SettingDescription
{
get

View File

@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModDifficultyAdjust : ModDifficultyAdjust
{
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
public BindableNumber<float> CircleSize { get; } = new BindableFloat
public BindableNumber<float> CircleSize { get; } = new BindableFloatWithLimitExtension
{
Precision = 0.1f,
MinValue = 0,
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
};
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
public BindableNumber<float> ApproachRate { get; } = new BindableFloat
public BindableNumber<float> ApproachRate { get; } = new BindableFloatWithLimitExtension
{
Precision = 0.1f,
MinValue = 0,
@ -31,6 +31,14 @@ namespace osu.Game.Rulesets.Osu.Mods
Value = 5,
};
protected override void ApplyLimits(bool extended)
{
base.ApplyLimits(extended);
CircleSize.MaxValue = extended ? 11 : 10;
ApproachRate.MaxValue = extended ? 11 : 10;
}
public override string SettingDescription
{
get

View File

@ -110,8 +110,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
double startTime = start.GetEndTime();
double duration = end.StartTime - startTime;
// Preempt time can go below 800ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
// This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear preempt function (see: OsuHitObject).
// Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
double preempt = PREEMPT * Math.Min(1, start.TimePreempt / OsuHitObject.PREEMPT_MIN);
fadeOutTime = startTime + fraction * duration;
fadeInTime = fadeOutTime - PREEMPT;
fadeInTime = fadeOutTime - preempt;
}
}
}

View File

@ -1,6 +1,7 @@
// 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.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
@ -25,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
internal const float BASE_SCORING_DISTANCE = 100;
/// <summary>
/// Minimum preempt time at AR=10.
/// </summary>
public const double PREEMPT_MIN = 450;
public double TimePreempt = 600;
public double TimeFadeIn = 400;
@ -112,8 +118,13 @@ namespace osu.Game.Rulesets.Osu.Objects
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
TimeFadeIn = 400; // as per osu-stable
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN);
// Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
// This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
// Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
// This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in.
TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}

View File

@ -68,12 +68,29 @@ namespace osu.Game.Tests.Online
Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25));
}
[Test]
public void TestDeserialiseDifficultyAdjustModWithExtendedLimits()
{
var apiMod = new APIMod(new TestModDifficultyAdjust
{
OverallDifficulty = { Value = 11 },
ExtendedLimits = { Value = true }
});
var deserialised = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
var converted = (TestModDifficultyAdjust)deserialised.ToMod(new TestRuleset());
Assert.That(converted.ExtendedLimits.Value, Is.True);
Assert.That(converted.OverallDifficulty.Value, Is.EqualTo(11));
}
private class TestRuleset : Ruleset
{
public override IEnumerable<Mod> GetModsFor(ModType type) => new Mod[]
{
new TestMod(),
new TestModTimeRamp(),
new TestModDifficultyAdjust()
};
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new System.NotImplementedException();
@ -135,5 +152,9 @@ namespace osu.Game.Tests.Online
Value = true
};
}
private class TestModDifficultyAdjust : ModDifficultyAdjust
{
}
}
}

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods
protected const int LAST_SETTING_ORDER = 2;
[SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)]
public BindableNumber<float> DrainRate { get; } = new BindableFloat
public BindableNumber<float> DrainRate { get; } = new BindableFloatWithLimitExtension
{
Precision = 0.1f,
MinValue = 0,
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mods
};
[SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)]
public BindableNumber<float> OverallDifficulty { get; } = new BindableFloat
public BindableNumber<float> OverallDifficulty { get; } = new BindableFloatWithLimitExtension
{
Precision = 0.1f,
MinValue = 0,
@ -53,6 +53,24 @@ namespace osu.Game.Rulesets.Mods
Value = 5,
};
[SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")]
public BindableBool ExtendedLimits { get; } = new BindableBool();
protected ModDifficultyAdjust()
{
ExtendedLimits.BindValueChanged(extend => ApplyLimits(extend.NewValue));
}
/// <summary>
/// Changes the difficulty adjustment limits. Occurs when the value of <see cref="ExtendedLimits"/> is changed.
/// </summary>
/// <param name="extended">Whether limits should extend beyond sane ranges.</param>
protected virtual void ApplyLimits(bool extended)
{
DrainRate.MaxValue = extended ? 11 : 10;
OverallDifficulty.MaxValue = extended ? 11 : 10;
}
public override string SettingDescription
{
get
@ -152,5 +170,62 @@ namespace osu.Game.Rulesets.Mods
TransferSettings(difficulty);
}
}
/// <summary>
/// A <see cref="BindableDouble"/> that extends its min/max values to support any assigned value.
/// </summary>
protected class BindableDoubleWithLimitExtension : BindableDouble
{
public override double Value
{
get => base.Value;
set
{
if (value < MinValue)
MinValue = value;
if (value > MaxValue)
MaxValue = value;
base.Value = value;
}
}
}
/// <summary>
/// A <see cref="BindableFloat"/> that extends its min/max values to support any assigned value.
/// </summary>
protected class BindableFloatWithLimitExtension : BindableFloat
{
public override float Value
{
get => base.Value;
set
{
if (value < MinValue)
MinValue = value;
if (value > MaxValue)
MaxValue = value;
base.Value = value;
}
}
}
/// <summary>
/// A <see cref="BindableInt"/> that extends its min/max values to support any assigned value.
/// </summary>
protected class BindableIntWithLimitExtension : BindableInt
{
public override int Value
{
get => base.Value;
set
{
if (value < MinValue)
MinValue = value;
if (value > MaxValue)
MaxValue = value;
base.Value = value;
}
}
}
}
}