Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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
82 changes: 40 additions & 42 deletions osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class TaikoDifficultyCalculator : DifficultyCalculator
private double strainLengthBonus;
private double patternMultiplier;

private bool isRelax;
private bool isConvert;

public override int Version => 20250306;
Expand All @@ -46,6 +47,7 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);

isConvert = beatmap.BeatmapInfo.Ruleset.OnlineID == 0;
isRelax = mods.Any(h => h is TaikoModRelax);

return new Skill[]
{
Expand Down Expand Up @@ -100,8 +102,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
if (beatmap.HitObjects.Count == 0)
return new TaikoDifficultyAttributes { Mods = mods };

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

var rhythm = skills.OfType<Rhythm>().Single();
var reading = skills.OfType<Reading>().Single();
var colour = skills.OfType<Colour>().Single();
Expand All @@ -122,7 +122,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat

strainLengthBonus = 1 + 0.15 * DifficultyCalculationUtils.ReverseLerp(staminaDifficultStrains, 1000, 1555);

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

// Calculate proportional contribution of each skill to the combinedRating.
Expand Down Expand Up @@ -159,30 +159,14 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
/// For each section, the peak strains of all separate skills are combined into a single peak strain for the section.
/// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
/// </remarks>
private double combinedDifficultyValue(Rhythm rhythm, Reading reading, Colour colour, Stamina stamina, bool isRelax, bool isConvert, out double consistencyFactor)
private double combinedDifficultyValue(Rhythm rhythm, Reading reading, Colour colour, Stamina stamina, out double consistencyFactor)
{
List<double> peaks = new List<double>();

var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList();
var readingPeaks = reading.GetCurrentStrainPeaks().ToList();
var colourPeaks = colour.GetCurrentStrainPeaks().ToList();
var staminaPeaks = stamina.GetCurrentStrainPeaks().ToList();

for (int i = 0; i < colourPeaks.Count; i++)
{
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier * patternMultiplier;
double readingPeak = readingPeaks[i] * reading_skill_multiplier;
double colourPeak = isRelax ? 0 : colourPeaks[i] * colour_skill_multiplier; // There is no colour difficulty in relax.
double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier * strainLengthBonus;
staminaPeak /= isConvert || isRelax ? 1.5 : 1.0; // Available finger count is increased by 150%, thus we adjust accordingly.

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

// 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.
if (peak > 0)
peaks.Add(peak);
}
List<double> peaks = combinePeaks(
rhythm.GetCurrentStrainPeaks().ToList(),
reading.GetCurrentStrainPeaks().ToList(),
colour.GetCurrentStrainPeaks().ToList(),
stamina.GetCurrentStrainPeaks().ToList()
);

double difficulty = 0;
double weight = 1;
Expand All @@ -193,33 +177,47 @@ private double combinedDifficultyValue(Rhythm rhythm, Reading reading, Colour co
weight *= 0.9;
}

consistencyFactor = calculateConsistencyFactor(peaks);
List<double> hitObjectStrainPeaks = combinePeaks(
rhythm.GetObjectStrains().ToList(),
reading.GetObjectStrains().ToList(),
colour.GetObjectStrains().ToList(),
stamina.GetObjectStrains().ToList()
);

// The average of the top 5% of strain peaks from hit objects.
double topAverageHitObjectStrain = hitObjectStrainPeaks.OrderDescending().Take(1 + hitObjectStrainPeaks.Count / 20).Average();

// Calculates a consistency factor as the sum of difficulty from hit objects compared to if every object were as hard as the hardest.
// The top average strain is used instead of the very hardest to prevent exceptionally hard objects lowering the factor.
consistencyFactor = hitObjectStrainPeaks.Sum() / (topAverageHitObjectStrain * hitObjectStrainPeaks.Count);

return difficulty;
}

/// <summary>
/// Calculates a consistency factor based on how 'spiked' the strain peaks are.
/// Higher values indicate more consistent difficulty, lower values indicate diff-spike heavy maps.
/// Combines lists of peak strains from multiple skills into a list of single peak strains for each section.
/// </summary>
private double calculateConsistencyFactor(List<double> peaks)
private List<double> combinePeaks(List<double> rhythmPeaks, List<double> readingPeaks, List<double> colourPeaks, List<double> staminaPeaks)
{
// If there are too few sections in a map, assume it is consistent.
if (peaks.Count < 3)
return 1.0;

List<double> sorted = peaks.OrderDescending().ToList();
var combinedPeaks = new List<double>();

double topPeak = sorted[0];
double secondTopPeak = sorted.Count > 1 ? sorted[1] : topPeak;
for (int i = 0; i < colourPeaks.Count; i++)
{
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier * patternMultiplier;
double readingPeak = readingPeaks[i] * reading_skill_multiplier;
double colourPeak = isRelax ? 0 : colourPeaks[i] * colour_skill_multiplier; // There is no colour difficulty in relax.
double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier * strainLengthBonus;
staminaPeak /= isConvert || isRelax ? 1.5 : 1.0; // Available finger count is increased by 150%, thus we adjust accordingly.

// Compute the average of the middle 50% of strain values.
double midAvg = sorted.Skip(sorted.Count / 4).Take(sorted.Count / 2).Average();
double peak = DifficultyCalculationUtils.Norm(2, DifficultyCalculationUtils.Norm(1.5, colourPeak, staminaPeak), rhythmPeak, readingPeak);

// A higher ratio means the top sections are much harder than the average, indicating inconsistency.
double spikeSeverity = (topPeak + secondTopPeak) / 2.0 / midAvg;
// 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.
if (peak > 0)
combinedPeaks.Add(peak);
}

return 1.0 / spikeSeverity;
return combinedPeaks;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ public class TaikoPerformanceAttributes : PerformanceAttributes
[JsonProperty("accuracy")]
public double Accuracy { get; set; }

[JsonProperty("effective_miss_count")]
public double EffectiveMissCount { get; set; }

[JsonProperty("estimated_unstable_rate")]
public double? EstimatedUnstableRate { get; set; }

Expand Down
24 changes: 8 additions & 16 deletions osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class TaikoPerformanceCalculator : PerformanceCalculator
private double clockRate;
private double greatHitWindow;

private double effectiveMissCount;
private double totalDifficultHits;

public TaikoPerformanceCalculator()
: base(new TaikoRuleset())
Expand Down Expand Up @@ -56,12 +56,8 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s

estimatedUnstableRate = computeDeviationUpperBound() * 10;

// Effective miss count is calculated by raising the fraction of hits missed to a power based on the map's consistency factor.
// This is because in less consistently difficult maps, each miss removes more of the map's total difficulty.
effectiveMissCount = totalHits * Math.Pow(
(double)countMiss / totalHits,
Math.Pow(taikoAttributes.ConsistencyFactor, 0.2)
);
// Total difficult hits measures the total difficulty of a map based on its consistency factor.
totalDifficultHits = totalHits * taikoAttributes.ConsistencyFactor;

// Converts are detected and omitted from mod-specific bonuses due to the scope of current difficulty calculation.
bool isConvert = score.BeatmapInfo!.Ruleset.OnlineID != 1;
Expand All @@ -73,7 +69,6 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
{
Difficulty = difficultyValue,
Accuracy = accuracyValue,
EffectiveMissCount = effectiveMissCount,
EstimatedUnstableRate = estimatedUnstableRate,
Total = difficultyValue + accuracyValue
};
Expand All @@ -86,14 +81,13 @@ private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes

difficultyValue *= 1 + 0.10 * Math.Max(0, attributes.StarRating - 10);

// Applies a bonus to maps with more total difficulty, calculating this with a map's total hits and consistency factor.
double totalDifficultHits = totalHits * Math.Pow(attributes.ConsistencyFactor, 0.5);
// Applies a bonus to maps with more total difficulty.
double lengthBonus = 1 + 0.25 * totalDifficultHits / (totalDifficultHits + 4000);
difficultyValue *= lengthBonus;

// Scales miss penalty by the total hits of a map, making misses more punishing on maps with fewer objects.
double missPenalty = Math.Pow(0.5, 30.0 / totalHits);
difficultyValue *= Math.Pow(missPenalty, effectiveMissCount);
// Scales miss penalty by the total difficult hits of a map, making misses more punishing on maps with less total difficulty.
double missPenalty = Math.Pow(0.5, 30.0 / totalDifficultHits);
difficultyValue *= Math.Pow(missPenalty, countMiss);

if (score.Mods.Any(m => m is ModHidden))
difficultyValue *= (isConvert) ? 1.025 : 1.1;
Expand Down Expand Up @@ -122,9 +116,7 @@ private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes a
accuracyValue *= 1.075;

// Applies a bonus to maps with more total difficulty, calculating this with a map's total hits and consistency factor.
double totalDifficultHits = totalHits * Math.Pow(attributes.ConsistencyFactor, 0.5);
double lengthBonus = 1 + 0.4 * totalDifficultHits / (totalDifficultHits + 4000);
accuracyValue *= lengthBonus;
accuracyValue *= 1 + 0.4 * totalDifficultHits / (totalDifficultHits + 4000);

// Applies a bonus to maps with more total memory required with HDFL.
double memoryLengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
Expand Down
2 changes: 2 additions & 0 deletions osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ private void startNewSectionFrom(double time, DifficultyHitObject current)
/// </summary>
public IEnumerable<double> GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak);

public IEnumerable<double> GetObjectStrains() => ObjectStrains;

/// <summary>
/// Returns the calculated difficulty value representing all <see cref="DifficultyHitObject"/>s that have been processed up to this point.
/// </summary>
Expand Down
Loading