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

Open
wants to merge 1 commit into
base: pp-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,24 +71,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes
[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 @@ -119,7 +101,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes
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 +111,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 @@ -144,15 +122,12 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> val
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
5 changes: 0 additions & 5 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
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
{
Expand All @@ -118,9 +116,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
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
34 changes: 25 additions & 9 deletions osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;

Expand Down Expand Up @@ -41,6 +45,11 @@ public class OsuPerformanceCalculator : PerformanceCalculator
/// </summary>
private double effectiveMissCount;

private double clockRate;
private double greatHitWindow;
private double okHitWindow;
private double mehHitWindow;

private double? speedDeviation;

public OsuPerformanceCalculator()
Expand Down Expand Up @@ -113,6 +122,17 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits);
}

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

HitWindows hitWindows = new OsuHitWindows();
hitWindows.SetDifficulty(score.BeatmapInfo!.Difficulty.OverallDifficulty);

greatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate;
okHitWindow = hitWindows.WindowFor(HitResult.Ok) / clockRate;
mehHitWindow = hitWindows.WindowFor(HitResult.Meh) / clockRate;

speedDeviation = calculateSpeedDeviation(osuAttributes);

double aimValue = computeAimValue(score, osuAttributes);
Expand Down Expand Up @@ -352,10 +372,6 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a

double objectCount = relevantCountGreat + relevantCountOk + relevantCountMeh + relevantCountMiss;

double hitWindowGreat = attributes.GreatHitWindow;
double hitWindowOk = attributes.OkHitWindow;
double hitWindowMeh = attributes.MehHitWindow;

// The probability that a player hits a circle is unknown, but we can estimate it to be
// the number of greats on circles divided by the number of circles, and then add one
// to the number of circles as a bias correction.
Expand All @@ -370,22 +386,22 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a

// Compute the deviation assuming greats and oks are normally distributed, and mehs are uniformly distributed.
// Begin with greats and oks first. Ignoring mehs, we can be 99% confident that the deviation is not higher than:
double deviation = hitWindowGreat / (Math.Sqrt(2) * DifficultyCalculationUtils.ErfInv(pLowerBound));
double deviation = greatHitWindow / (Math.Sqrt(2) * DifficultyCalculationUtils.ErfInv(pLowerBound));

double randomValue = Math.Sqrt(2 / Math.PI) * hitWindowOk * Math.Exp(-0.5 * Math.Pow(hitWindowOk / deviation, 2))
/ (deviation * DifficultyCalculationUtils.Erf(hitWindowOk / (Math.Sqrt(2) * deviation)));
double randomValue = Math.Sqrt(2 / Math.PI) * okHitWindow * Math.Exp(-0.5 * Math.Pow(okHitWindow / deviation, 2))
/ (deviation * DifficultyCalculationUtils.Erf(okHitWindow / (Math.Sqrt(2) * deviation)));

deviation *= Math.Sqrt(1 - randomValue);

// Value deviation approach as greatCount approaches 0
double limitValue = hitWindowOk / Math.Sqrt(3);
double limitValue = okHitWindow / Math.Sqrt(3);

// If precision is not enough to compute true deviation - use limit value
if (pLowerBound == 0 || randomValue >= 1 || deviation > limitValue)
deviation = limitValue;

// Then compute the variance for mehs.
double mehVariance = (hitWindowMeh * hitWindowMeh + hitWindowOk * hitWindowMeh + hitWindowOk * hitWindowOk) / 3;
double mehVariance = (mehHitWindow * mehHitWindow + okHitWindow * mehHitWindow + okHitWindow * okHitWindow) / 3;

// Find the total deviation.
deviation = Math.Sqrt(((relevantCountGreat + relevantCountOk) * Math.Pow(deviation, 2) + relevantCountMeh * mehVariance) / (relevantCountGreat + relevantCountOk + relevantCountMeh));
Expand Down
1 change: 0 additions & 1 deletion osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public class DifficultyAttributes
protected const int ATTRIB_ID_OK_HIT_WINDOW = 27;
protected const int ATTRIB_ID_MONO_STAMINA_FACTOR = 29;
protected const int ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT = 31;
protected const int ATTRIB_ID_MEH_HIT_WINDOW = 33;

/// <summary>
/// The mods which were applied to the beatmap.
Expand Down
Loading