Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calculate hit windows in performance calculator instead of databased difficulty attributes #31735

Merged
merged 9 commits into from
Feb 7, 2025
12 changes: 0 additions & 12 deletions osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,27 @@
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;

namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyAttributes : DifficultyAttributes
{
/// <summary>
/// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
/// <remarks>
/// Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing.
/// </remarks>
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }

public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;

// Todo: osu!catch should not output star rating in the 'aim' attribute.
yield return (ATTRIB_ID_AIM, StarRating);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
{
base.FromDatabaseAttributes(values, onlineInfo);

StarRating = values[ATTRIB_ID_AIM];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,10 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
if (beatmap.HitObjects.Count == 0)
return new CatchDifficultyAttributes { Mods = mods };

// this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;

CatchDifficultyAttributes attributes = new CatchDifficultyAttributes
{
StarRating = Math.Sqrt(skills.OfType<Movement>().Single().DifficultyValue()) * difficulty_multiplier,
Mods = mods,
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
MaxCombo = beatmap.GetMaxCombo(),
};

Expand Down
17 changes: 16 additions & 1 deletion osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

using System;
using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
Expand Down Expand Up @@ -50,7 +53,19 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
if (catchAttributes.MaxCombo > 0)
value *= Math.Min(Math.Pow(score.MaxCombo, 0.8) / Math.Pow(catchAttributes.MaxCombo, 0.8), 1.0);

double approachRate = catchAttributes.ApproachRate;
var difficulty = score.BeatmapInfo!.Difficulty.Clone();

score.Mods.OfType<IApplicableToDifficulty>().ForEach(m => m.ApplyToDifficulty(difficulty));

var track = new TrackVirtual(10000);
score.Mods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
double clockRate = track.Rate;

// this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450) / clockRate;

double approachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0;

double approachRateFactor = 1.0;
if (approachRate > 9.0)
approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9
Expand Down
12 changes: 0 additions & 12 deletions osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,26 @@
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;

namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaDifficultyAttributes : DifficultyAttributes
{
/// <summary>
/// The hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
/// <remarks>
/// Rate-adjusting mods do not affect the hit window at all in osu-stable.
/// </remarks>
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; }

public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;

yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
{
base.FromDatabaseAttributes(values, onlineInfo);

StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
}
}
}
29 changes: 0 additions & 29 deletions osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ public class ManiaDifficultyCalculator : DifficultyCalculator
private const double difficulty_multiplier = 0.018;

private readonly bool isForCurrentRuleset;
private readonly double originalOverallDifficulty;

public override int Version => 20241007;

public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.MatchesOnlineID(ruleset);
originalOverallDifficulty = beatmap.BeatmapInfo.Difficulty.OverallDifficulty;
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
Expand All @@ -50,9 +48,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
{
StarRating = skills.OfType<Strain>().Single().DifficultyValue() * difficulty_multiplier,
Mods = mods,
// In osu-stable mania, rate-adjustment mods don't affect the hit window.
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject),
};

Expand Down Expand Up @@ -124,29 +119,5 @@ protected override Mod[] DifficultyAdjustmentMods
}).ToArray();
}
}

private double getHitWindow300(Mod[] mods)
{
if (isForCurrentRuleset)
{
double od = Math.Min(10.0, Math.Max(0, 10.0 - originalOverallDifficulty));
return applyModAdjustments(34 + 3 * od, mods);
}

if (Math.Round(originalOverallDifficulty) > 4)
return applyModAdjustments(34, mods);

return applyModAdjustments(47, mods);

static double applyModAdjustments(double value, Mod[] mods)
{
if (mods.Any(m => m is ManiaModHardRock))
value /= 1.4;
else if (mods.Any(m => m is ManiaModEasy))
value *= 1.4;

return value;
}
}
}
}
41 changes: 0 additions & 41 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,36 +59,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes
[JsonProperty("speed_difficult_strain_count")]
public double SpeedDifficultStrainCount { get; set; }

/// <summary>
/// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }

/// <summary>
/// The perceived overall difficulty inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
[JsonProperty("overall_difficulty")]
public double OverallDifficulty { get; set; }

/// <summary>
/// The perceived hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; }

/// <summary>
/// The perceived hit window for an OK hit inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
[JsonProperty("ok_hit_window")]
public double OkHitWindow { get; set; }

/// <summary>
/// The perceived hit window for a MEH hit inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
[JsonProperty("meh_hit_window")]
public double MehHitWindow { get; set; }

/// <summary>
/// The beatmap's drain rate. This doesn't scale with rate-adjusting mods.
/// </summary>
Expand Down Expand Up @@ -116,10 +86,7 @@ public class OsuDifficultyAttributes : DifficultyAttributes

yield return (ATTRIB_ID_AIM, AimDifficulty);
yield return (ATTRIB_ID_SPEED, SpeedDifficulty);
yield return (ATTRIB_ID_OVERALL_DIFFICULTY, OverallDifficulty);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);

if (ShouldSerializeFlashlightDifficulty())
yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty);
Expand All @@ -130,9 +97,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes
yield return (ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT, SpeedDifficultStrainCount);
yield return (ATTRIB_ID_SPEED_NOTE_COUNT, SpeedNoteCount);
yield return (ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT, AimDifficultSliderCount);

yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow);
yield return (ATTRIB_ID_MEH_HIT_WINDOW, MehHitWindow);
}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
Expand All @@ -141,18 +105,13 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> val

AimDifficulty = values[ATTRIB_ID_AIM];
SpeedDifficulty = values[ATTRIB_ID_SPEED];
OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
AimDifficultStrainCount = values[ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT];
SpeedDifficultStrainCount = values[ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT];
SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT];
AimDifficultSliderCount = values[ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT];
OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW];
MehHitWindow = values[ATTRIB_ID_MEH_HIT_WINDOW];
DrainRate = onlineInfo.DrainRate;
HitCircleCount = onlineInfo.CircleCount;
SliderCount = onlineInfo.SliderCount;
Expand Down
15 changes: 0 additions & 15 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;

namespace osu.Game.Rulesets.Osu.Difficulty
{
Expand Down Expand Up @@ -90,20 +88,12 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4)
: 0;

double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
double drainRate = beatmap.Difficulty.DrainRate;

int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
int sliderCount = beatmap.HitObjects.Count(h => h is Slider);
int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner);

HitWindows hitWindows = new OsuHitWindows();
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);

double hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate;
double hitWindowOk = hitWindows.WindowFor(HitResult.Ok) / clockRate;
double hitWindowMeh = hitWindows.WindowFor(HitResult.Meh) / clockRate;

OsuDifficultyAttributes attributes = new OsuDifficultyAttributes
{
StarRating = starRating,
Expand All @@ -116,11 +106,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
SliderFactor = sliderFactor,
AimDifficultStrainCount = aimDifficultyStrainCount,
SpeedDifficultStrainCount = speedDifficultyStrainCount,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
GreatHitWindow = hitWindowGreat,
OkHitWindow = hitWindowOk,
MehHitWindow = hitWindowMeh,
DrainRate = drainRate,
MaxCombo = beatmap.GetMaxCombo(),
HitCircleCount = hitCirclesCount,
Expand Down
Loading
Loading