Skip to content

Commit

Permalink
New Difficulty Factor + Length Bonus Fix
Browse files Browse the repository at this point in the history
  • Loading branch information
TheDark98 committed Jan 15, 2025
2 parents 694b40a + 4c8a193 commit c3ea76f
Show file tree
Hide file tree
Showing 11 changed files with 46 additions and 82 deletions.
12 changes: 6 additions & 6 deletions osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ public class OsuDifficultyCalculatorTest : DifficultyCalculatorTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu.Tests";

[TestCase(6.6860329680488437d, 239, "diffcalc-test")]
[TestCase(1.4485740324170036d, 54, "zero-length-sliders")]
[TestCase(6.7331304290522747d, 239, "diffcalc-test")]
[TestCase(1.4602604078137214d, 54, "zero-length-sliders")]
[TestCase(0.43052813047866129d, 4, "very-fast-slider")]
[TestCase(0.14143808967817237d, 2, "nan-slider")]
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
=> base.Test(expectedStarRating, expectedMaxCombo, name);

[TestCase(9.6300773538770041d, 239, "diffcalc-test")]
[TestCase(1.7550155729445993d, 54, "zero-length-sliders")]
[TestCase(9.6779397290273756d, 239, "diffcalc-test")]
[TestCase(1.7691451263718989d, 54, "zero-length-sliders")]
[TestCase(0.55785578988249407d, 4, "very-fast-slider")]
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());

[TestCase(6.6860329680488437d, 239, "diffcalc-test")]
[TestCase(1.4485740324170036d, 54, "zero-length-sliders")]
[TestCase(6.7331304290522747d, 239, "diffcalc-test")]
[TestCase(1.4602604078137214d, 54, "zero-length-sliders")]
[TestCase(0.43052813047866129d, 4, "very-fast-slider")]
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with

// Penalize angle repetition.
wideAngleBonus *= 1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3));
acuteAngleBonus *= 0.1 + 0.9 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastAngle), 3)));
acuteAngleBonus *= 0.08 + 0.92 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastAngle), 3)));

// Apply full wide angle bonus for distance more than one diameter
wideAngleBonus *= angleBonus * DifficultyCalculationUtils.Smootherstep(osuCurrObj.LazyJumpDistance, 0, diameter);
Expand Down
8 changes: 4 additions & 4 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ public class OsuDifficultyAttributes : DifficultyAttributes
/// <summary>
/// The difficulty factor corresponding to the aim skill.
/// </summary>
[JsonProperty("aim_difficulty_factor")]
public double AimDifficultyFactor { get; set; }
[JsonProperty("aim_consistency_factor")]
public double AimConsistencyFactor { get; set; }

/// <summary>
/// The number of <see cref="Slider"/>s weighted by difficulty.
Expand All @@ -41,8 +41,8 @@ public class OsuDifficultyAttributes : DifficultyAttributes
/// <summary>
/// The difficulty factor corresponding to the speed skill.
/// </summary>
[JsonProperty("speed_difficulty_factor")]
public double SpeedDifficultyFactor { get; set; }
[JsonProperty("speed_consistency_factor")]
public double SpeedConsistencyFactor { get; set; }

/// <summary>
/// The number of clickable objects weighted by difficulty.
Expand Down
8 changes: 4 additions & 4 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
double difficultSliders = ((Aim)skills[0]).GetDifficultSliders();
double flashlightRating = 0.0;

double aimDifficultyFactor = skills[0].DifficultyFactor;
double speedDifficultyFactor = skills[2].DifficultyFactor;
double aimConsistencyFactor = skills[0].ConsistencyFactor;
double speedConsistencyFactor = skills[2].ConsistencyFactor;

if (mods.Any(h => h is OsuModFlashlight))
flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier;
Expand Down Expand Up @@ -110,10 +110,10 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
StarRating = starRating,
Mods = mods,
AimDifficulty = aimRating,
AimDifficultyFactor = aimDifficultyFactor,
AimConsistencyFactor = aimConsistencyFactor,
AimDifficultSliderCount = difficultSliders,
SpeedDifficulty = speedRating,
SpeedDifficultyFactor = speedDifficultyFactor,
SpeedConsistencyFactor = speedConsistencyFactor,
SpeedNoteCount = speedNotes,
FlashlightDifficulty = flashlightRating,
SliderFactor = sliderFactor,
Expand Down
24 changes: 9 additions & 15 deletions osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
Expand Down Expand Up @@ -41,7 +42,7 @@ public class OsuPerformanceCalculator : PerformanceCalculator
/// <summary>
/// The bonus multiplier is a basic multiplier that indicate how strong the impact of Difficulty Factor is.
/// </summary>
private const double bonus_multiplier = 0.1;
private const double bonus_multiplier = 0.3;

/// <summary>
/// Amount of missed slider tails that don't break combo. Will only be correct on non-classic scores
Expand Down Expand Up @@ -127,14 +128,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s

speedDeviation = calculateSpeedDeviation(osuAttributes);

if (totalHits > 2000)
{
lengthBonusBase = 1.3 * Math.Max((Math.Log(10.0 + totalHits / 1000.0) - 1.35) * 0.45 + 0.5, 1.0);
}
else
{
lengthBonusBase = Math.Max((Math.Log(10.0 + totalHits / 1000.0) - 1.35) * 0.45 + 0.5, 1.0);
}
lengthBonusBase = Math.Log(10.0 + totalHits / 1000.0) - 1.2;

double aimValue = computeAimValue(score, osuAttributes);
double speedValue = computeSpeedValue(score, osuAttributes);
Expand Down Expand Up @@ -191,9 +185,9 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut

double aimValue = OsuStrainSkill.DifficultyToPerformance(aimDifficulty);

double approachRateBonus = score.Mods.Any(h => h is OsuModRelax) ? 0.0 : attributes.ApproachRate > 10.33 ? 0.3 * (attributes.ApproachRate - 10.33) : attributes.ApproachRate < 8.0 ? 0.05 * (8.0 - attributes.ApproachRate) : 0.0; //AR bonus for higher and lower AR
double approachRateBonus = score.Mods.Any(h => h is OsuModRelax) ? 0.0 : attributes.ApproachRate > 10.33 ? 0.2 * (attributes.ApproachRate - 10.33) : attributes.ApproachRate < 8.0 ? 0.02 * (8.0 - attributes.ApproachRate) : 0.0; //AR bonus for higher and lower AR

aimValue *= LengthBonusMultiplier(lengthBonusBase + approachRateBonus, attributes.AimDifficultyFactor, bonus_multiplier);
aimValue *= lengthBonusBase * LengthBonusMultiplier(0.95, attributes.AimConsistencyFactor, bonus_multiplier) * (1.0 + approachRateBonus);

if (effectiveMissCount > 0)
aimValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount);
Expand All @@ -220,9 +214,9 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib

double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty);

double approachRateBonus = attributes.ApproachRate > 10.33 ? 0.3 * (attributes.ApproachRate - 10.33) : 0.0;
double approachRateBonus = attributes.ApproachRate > 10.33 ? 0.2 * (attributes.ApproachRate - 10.33) : 0.0;

speedValue *= LengthBonusMultiplier(lengthBonusBase + approachRateBonus, attributes.SpeedDifficultyFactor, bonus_multiplier);
speedValue *= LengthBonusMultiplier(lengthBonusBase, attributes.SpeedConsistencyFactor, bonus_multiplier) * (1.0 + approachRateBonus);

if (effectiveMissCount > 0)
speedValue *= calculateMissPenalty(effectiveMissCount, attributes.SpeedDifficultStrainCount);
Expand Down Expand Up @@ -413,8 +407,8 @@ private double calculateSpeedHighDeviationNerf(OsuDifficultyAttributes attribute
const double scale = 50;
double adjustedSpeedValue = scale * (Math.Log((speedValue - excessSpeedDifficultyCutoff) / scale + 1) + excessSpeedDifficultyCutoff / scale);

// 200 UR and less are considered tapped correctly to ensure that normal scores will be punished as little as possible
double lerp = 1 - Math.Clamp((speedDeviation.Value - 20) / (24 - 20), 0, 1);
// 220 UR and less are considered tapped correctly to ensure that normal scores will be punished as little as possible
double lerp = 1 - DifficultyCalculationUtils.ReverseLerp(speedDeviation.Value, 22.0, 27.0);
adjustedSpeedValue = double.Lerp(adjustedSpeedValue, speedValue, lerp);

return adjustedSpeedValue / speedValue;
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public Aim(Mod[] mods, bool withSliders)

private double currentStrain;

private double skillMultiplier => 25.18;
private double skillMultiplier => 25.6;
private double strainDecayBase => 0.15;

private readonly List<double> sliderStrains = new List<double>();
Expand Down
17 changes: 9 additions & 8 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using osu.Game.Rulesets.Mods;
using System.Linq;
using osu.Framework.Utils;
using SharpCompress;
using osu.Framework.Extensions.ObjectExtensions;

namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
Expand Down Expand Up @@ -39,16 +41,15 @@ public override double DifficultyValue()

List<double> strains = peaks.OrderDescending().ToList();

// We select only the hardest 20% of picks in strains to ensure greater value in the most difficult sections of the map.
List<double> hardStrains = strains.OrderDescending().ToList().GetRange(0, strains.Count / 10 * 2);

//We select only the moderate 20% of picks in strains to provide greater value in the more moderate sections of the map.
List<double> midStrains = strains.OrderDescending().ToList().GetRange(strains.Count / 10 * 4, strains.Count / 10 * 6);

// We can calculate the difficulty factor by doing average pick difficulty / max peak difficulty.
// It resoult in a value that rappresent the consistency for all peaks (0 excluded) in a range number from 0 to 1.
DifficultyFactor = peaks.Average() / peaks.Max();

// We are reducing the highest strains first to account for extreme difficulty spikes
for (int i = 0; i < Math.Min(strains.Count, ReducedSectionCount); i++)
{
double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp((float)i / ReducedSectionCount, 0, 1)));
strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale);
}
ConsistencyFactor = midStrains.Average() / hardStrains.Average();

// Difficulty is the weighted sum of the highest strains from every section.
// We're sorting from highest to lowest strain.
Expand Down
48 changes: 8 additions & 40 deletions osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,53 +123,21 @@ private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes a
/// </summary>
private double? computeDeviationUpperBound(TaikoDifficultyAttributes attributes)
{
if (totalSuccessfulHits == 0 || attributes.GreatHitWindow <= 0)
if (countGreat == 0 || attributes.GreatHitWindow <= 0)
return null;

double h300 = attributes.GreatHitWindow;
double h100 = attributes.OkHitWindow;

const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed).

double? deviationGreatWindow = calcDeviationGreatWindow();
double? deviationGoodWindow = calcDeviationGoodWindow();

return deviationGreatWindow is null ? deviationGoodWindow : Math.Min(deviationGreatWindow.Value, deviationGoodWindow!.Value);

// The upper bound on deviation, calculated with the ratio of 300s to objects, and the great hit window.
double? calcDeviationGreatWindow()
{
if (countGreat == 0) return null;

double n = totalHits;

// Proportion of greats hit.
double p = countGreat / n;

// We can be 99% confident that p is at least this value.
double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4);

// We can be 99% confident that the deviation is not higher than:
return h300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound));
}

// The upper bound on deviation, calculated with the ratio of 300s + 100s to objects, and the good hit window.
// This will return a lower value than the first method when the number of 100s is high, but the miss count is low.
double? calcDeviationGoodWindow()
{
if (totalSuccessfulHits == 0) return null;

double n = totalHits;
double n = totalHits;

// Proportion of greats + goods hit.
double p = Math.Max(0, totalSuccessfulHits - 0.0005 * countOk) / n;
// Proportion of greats hit.
double p = countGreat / n;

// We can be 99% confident that p is at least this value.
double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4);
// We can be 99% confident that p is at least this value.
double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4);

// We can be 99% confident that the deviation is not higher than:
return h100 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound));
}
// We can be 99% confident that the deviation is not higher than:
return attributes.GreatHitWindow / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound));
}

private int totalHits => countGreat + countOk + countMeh + countMiss;
Expand Down
3 changes: 2 additions & 1 deletion osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Threading;
using System.Threading.Tasks;
using osu.Game.Beatmaps;
Expand Down Expand Up @@ -36,6 +37,6 @@ public PerformanceAttributes Calculate(ScoreInfo score, IWorkingBeatmap beatmap)
/// <summary>
/// Calculating the length bonus as a multiplier considering also the Difficulty Factor.
/// </summary>
protected virtual double LengthBonusMultiplier(double offsetLengthBonus, double difficultyFactor, double multiplierDifficultyFactor) => offsetLengthBonus + difficultyFactor * multiplierDifficultyFactor;
protected virtual double LengthBonusMultiplier(double lengthBonusBase, double difficultyFactor, double multiplierDifficultyFactor) => Math.Max(lengthBonusBase * (0.95 + difficultyFactor * multiplierDifficultyFactor), 1.0);
}
}
2 changes: 1 addition & 1 deletion osu.Game/Rulesets/Difficulty/Skills/Skill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected Skill(Mod[] mods)
/// <summary>
/// Value that rappresent the consistency for all <see cref="DifficultyHitObject"/>s (0 excluded) that have been processed up to this point in a range number from 0 to 1.
/// </summary>
public double DifficultyFactor;
public double ConsistencyFactor;

/// <summary>
/// Process a <see cref="DifficultyHitObject"/>.
Expand Down
2 changes: 1 addition & 1 deletion osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public override double DifficultyValue()

// We can calculate the difficulty factor by doing average pick difficulty / max peak difficulty.
// It resoult in a value that rappresent the consistency for all peaks (0 excluded) in a range number from 0 to 1.
DifficultyFactor = peaks.Average() / peaks.Max();
ConsistencyFactor = peaks.Average() / peaks.Max();

// Difficulty is the weighted sum of the highest strains from every section.
// We're sorting from highest to lowest strain.
Expand Down

0 comments on commit c3ea76f

Please sign in to comment.