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

Refactor TaikoDifficultyCalculator and add DifficultStrain attributes #31191

Merged
merged 7 commits into from
Dec 19, 2024
Merged
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
13 changes: 8 additions & 5 deletions osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ public class TaikoDifficultyAttributes : DifficultyAttributes
[JsonProperty("colour_difficulty")]
public double ColourDifficulty { get; set; }

/// <summary>
/// The difficulty corresponding to the hardest parts of the map.
/// </summary>
[JsonProperty("peak_difficulty")]
public double PeakDifficulty { get; set; }
[JsonProperty("rhythm_difficult_strains")]
public double RhythmTopStrains { get; set; }

[JsonProperty("colour_difficult_strains")]
public double ColourTopStrains { get; set; }

[JsonProperty("stamina_difficult_strains")]
public double StaminaTopStrains { get; set; }

/// <summary>
/// The perceived hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc).
Expand Down
75 changes: 41 additions & 34 deletions osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
Expand Down Expand Up @@ -53,18 +54,25 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
{
List<DifficultyHitObject> difficultyHitObjects = new List<DifficultyHitObject>();
List<TaikoDifficultyHitObject> centreObjects = new List<TaikoDifficultyHitObject>();
List<TaikoDifficultyHitObject> rimObjects = new List<TaikoDifficultyHitObject>();
List<TaikoDifficultyHitObject> noteObjects = new List<TaikoDifficultyHitObject>();
var difficultyHitObjects = new List<DifficultyHitObject>();
var centreObjects = new List<TaikoDifficultyHitObject>();
var rimObjects = new List<TaikoDifficultyHitObject>();
var noteObjects = new List<TaikoDifficultyHitObject>();

// Generate TaikoDifficultyHitObjects from the beatmap's hit objects.
for (int i = 2; i < beatmap.HitObjects.Count; i++)
{
difficultyHitObjects.Add(
new TaikoDifficultyHitObject(
beatmap.HitObjects[i], beatmap.HitObjects[i - 1], beatmap.HitObjects[i - 2], clockRate, difficultyHitObjects,
centreObjects, rimObjects, noteObjects, difficultyHitObjects.Count)
);
difficultyHitObjects.Add(new TaikoDifficultyHitObject(
beatmap.HitObjects[i],
beatmap.HitObjects[i - 1],
beatmap.HitObjects[i - 2],
clockRate,
difficultyHitObjects,
centreObjects,
rimObjects,
noteObjects,
difficultyHitObjects.Count
));
}

TaikoColourDifficultyPreprocessor.ProcessAndAssign(difficultyHitObjects);
Expand All @@ -79,28 +87,33 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat

bool isRelax = mods.Any(h => h is TaikoModRelax);

Colour colour = (Colour)skills.First(x => x is Colour);
Rhythm rhythm = (Rhythm)skills.First(x => x is Rhythm);
Colour colour = (Colour)skills.First(x => x is Colour);
Stamina stamina = (Stamina)skills.First(x => x is Stamina);
Stamina singleColourStamina = (Stamina)skills.Last(x => x is Stamina);

double colourRating = colour.DifficultyValue() * colour_skill_multiplier;
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
double colourRating = colour.DifficultyValue() * colour_skill_multiplier;
double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier;
double monoStaminaRating = singleColourStamina.DifficultyValue() * stamina_skill_multiplier;
double monoStaminaFactor = staminaRating == 0 ? 1 : Math.Pow(monoStaminaRating / staminaRating, 5);

double rhythmDifficultStrains = rhythm.CountTopWeightedStrains();
double colourDifficultStrains = colour.CountTopWeightedStrains();
double staminaDifficultStrains = stamina.CountTopWeightedStrains();

double combinedRating = combinedDifficultyValue(rhythm, colour, stamina, isRelax);
double starRating = rescale(combinedRating * 1.4);

// TODO: This is temporary measure as we don't detect abuse of multiple-input playstyles of converts within the current system.
// Converts are penalised outside the scope of difficulty calculation, as our assumptions surrounding standard play-styles becomes out-of-scope.
if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0)
{
starRating *= 0.925;

// For maps with either relax or low colour variance and high stamina requirement, multiple inputs are more likely to be abused.
// For maps with relax, multiple inputs are more likely to be abused.
if (isRelax)
starRating *= 0.60;
// For maps with either relax or low colour variance and high stamina requirement, multiple inputs are more likely to be abused.
else if (colourRating < 2 && staminaRating > 8)
starRating *= 0.80;
}
Expand All @@ -112,11 +125,13 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
{
StarRating = starRating,
Mods = mods,
StaminaDifficulty = staminaRating,
MonoStaminaFactor = monoStaminaFactor,
RhythmDifficulty = rhythmRating,
ColourDifficulty = colourRating,
PeakDifficulty = combinedRating,
StaminaDifficulty = staminaRating,
MonoStaminaFactor = monoStaminaFactor,
StaminaTopStrains = staminaDifficultStrains,
RhythmTopStrains = rhythmDifficultStrains,
ColourTopStrains = colourDifficultStrains,
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
OkHitWindow = hitWindows.WindowFor(HitResult.Ok) / clockRate,
MaxCombo = beatmap.GetMaxCombo(),
Expand All @@ -125,17 +140,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
return attributes;
}

/// <summary>
/// Applies a final re-scaling of the star rating.
/// </summary>
/// <param name="sr">The raw star rating value before re-scaling.</param>
private double rescale(double sr)
{
if (sr < 0) return sr;

return 10.43 * Math.Log(sr / 8 + 1);
}

/// <summary>
/// Returns the combined star rating of the beatmap, calculated using peak strains from all sections of the map.
/// </summary>
Expand All @@ -153,8 +157,8 @@ private double combinedDifficultyValue(Rhythm rhythm, Colour colour, Stamina sta

for (int i = 0; i < colourPeaks.Count; i++)
{
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier;
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier;

if (isRelax)
Expand All @@ -163,8 +167,7 @@ private double combinedDifficultyValue(Rhythm rhythm, Colour colour, Stamina sta
staminaPeak /= 1.5; // Stamina difficulty is decreased with an increased available finger count.
}

double peak = norm(1.5, colourPeak, staminaPeak);
peak = norm(2, peak, rhythmPeak);
double peak = DifficultyCalculationUtils.Norm(2, DifficultyCalculationUtils.Norm(1.5, colourPeak, staminaPeak), rhythmPeak);

// Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
// These sections will not contribute to the difficulty.
Expand All @@ -185,10 +188,14 @@ private double combinedDifficultyValue(Rhythm rhythm, Colour colour, Stamina sta
}

/// <summary>
/// Returns the <i>p</i>-norm of an <i>n</i>-dimensional vector.
/// Applies a final re-scaling of the star rating.
/// </summary>
/// <param name="p">The value of <i>p</i> to calculate the norm for.</param>
/// <param name="values">The coefficients of the vector.</param>
private double norm(double p, params double[] values) => Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p);
/// <param name="sr">The raw star rating value before re-scaling.</param>
private double rescale(double sr)
{
if (sr < 0) return sr;

return 10.43 * Math.Log(sr / 8 + 1);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Linq;

namespace osu.Game.Rulesets.Difficulty.Utils
{
Expand Down Expand Up @@ -46,5 +47,13 @@ public static double MillisecondsToBPM(double ms, int delimiter = 4)
/// <param name="exponent">Exponent</param>
/// <returns>The output of logistic function</returns>
public static double Logistic(double exponent, double maxValue = 1) => maxValue / (1 + Math.Exp(exponent));

/// <summary>
/// Returns the <i>p</i>-norm of an <i>n</i>-dimensional vector (https://en.wikipedia.org/wiki/Norm_(mathematics))
/// </summary>
/// <param name="p">The value of <i>p</i> to calculate the norm for.</param>
/// <param name="values">The coefficients of the vector.</param>
/// <returns>The <i>p</i>-norm of the vector.</returns>
public static double Norm(double p, params double[] values) => Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p);
}
}
Loading