1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-12 23:53:21 +08:00

ported CSR correctly

This commit is contained in:
Givikap120 2024-10-07 23:32:00 +03:00
parent 7f3093ee49
commit e72ae6d9ae
7 changed files with 67 additions and 49 deletions

View File

@ -70,6 +70,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
[JsonProperty("speed_difficult_strain_count")] [JsonProperty("speed_difficult_strain_count")]
public double SpeedDifficultStrainCount { get; set; } public double SpeedDifficultStrainCount { get; set; }
[JsonProperty("low_ar_difficult_strain_count")]
public double LowArDifficultStrainCount { get; set; }
[JsonProperty("hidden_difficult_strain_count")]
public double HiddenDifficultStrainCount { get; set; }
/// <summary> /// <summary>
/// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc). /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary> /// </summary>

View File

@ -51,11 +51,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
double hiddenDifficultyStrainCount = 0;
double baseReadingHiddenPerformance = 0.0; double baseReadingHiddenPerformance = 0.0;
if (mods.Any(h => h is OsuModHidden)) if (mods.Any(h => h is OsuModHidden))
{ {
hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * DIFFICULTY_MULTIPLIER; hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
baseReadingHiddenPerformance = ReadingHidden.DifficultyToPerformance(hiddenRating); baseReadingHiddenPerformance = ReadingHidden.DifficultyToPerformance(hiddenRating);
hiddenDifficultyStrainCount = skills[5].CountDifficultStrains();
} }
double baseFlashlightPerformance = 0.0; double baseFlashlightPerformance = 0.0;
@ -63,8 +65,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating); baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating);
} }
double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains();
double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(); double aimDifficultyStrainCount = skills[0].CountDifficultStrains();
double speedDifficultyStrainCount = skills[2].CountDifficultStrains();
double lowArDifficultyStrainCount = skills[4].CountDifficultStrains();
if (mods.Any(m => m is OsuModTouchDevice)) if (mods.Any(m => m is OsuModTouchDevice))
{ {
@ -135,13 +139,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
SliderFactor = sliderFactor, SliderFactor = sliderFactor,
AimDifficultStrainCount = aimDifficultyStrainCount, AimDifficultStrainCount = aimDifficultyStrainCount,
SpeedDifficultStrainCount = speedDifficultyStrainCount, SpeedDifficultStrainCount = speedDifficultyStrainCount,
LowArDifficultStrainCount = lowArDifficultyStrainCount,
HiddenDifficultStrainCount = hiddenDifficultyStrainCount,
ApproachRate = IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, 1800, 1200, 450), ApproachRate = IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, 1800, 1200, 450),
OverallDifficulty = (80 - hitWindowGreat) / 6, OverallDifficulty = (80 - hitWindowGreat) / 6,
DrainRate = drainRate, DrainRate = drainRate,
MaxCombo = beatmap.GetMaxCombo(), MaxCombo = beatmap.GetMaxCombo(),
HitCircleCount = hitCirclesCount, HitCircleCount = hitCirclesCount,
SliderCount = sliderCount, SliderCount = sliderCount,
SpinnerCount = spinnerCount, SpinnerCount = spinnerCount
}; };
return attributes; return attributes;

View File

@ -216,6 +216,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (!usingClassicSliderAccuracy) if (!usingClassicSliderAccuracy)
amountHitObjectsWithAccuracy += attributes.SliderCount; amountHitObjectsWithAccuracy += attributes.SliderCount;
if (score.Mods.OfType<OsuModClassic>().All(m => !m.NoSliderHeadAccuracy.Value))
amountHitObjectsWithAccuracy += attributes.SliderCount;
if (amountHitObjectsWithAccuracy > 0) if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
else else
@ -289,9 +292,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0) if (effectiveMissCount > 0)
readingValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); readingValue *= calculateMissPenalty(effectiveMissCount, attributes.LowArDifficultStrainCount);
readingValue *= getComboScalingFactor(attributes);
// Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework. // Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework.
readingValue *= accuracy * accuracy; readingValue *= accuracy * accuracy;
@ -305,12 +306,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
double highARValue = OsuStrainSkill.DifficultyToPerformance(attributes.ReadingDifficultyHighAR); double highARValue = OsuStrainSkill.DifficultyToPerformance(attributes.ReadingDifficultyHighAR);
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
highARValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount);
highARValue *= getComboScalingFactor(attributes);
// Approximate how much of high AR difficulty is aim // Approximate how much of high AR difficulty is aim
double aimPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty); double aimPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty);
double speedPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); double speedPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
@ -330,6 +325,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimPartValue *= sliderNerfFactor; aimPartValue *= sliderNerfFactor;
} }
if (effectiveMissCount > 0)
aimPartValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount);
aimPartValue *= accuracy; aimPartValue *= accuracy;
// It is important to consider accuracy difficulty when scaling with accuracy. // It is important to consider accuracy difficulty when scaling with accuracy.
aimPartValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; aimPartValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500;
@ -345,6 +343,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk));
double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0);
if (effectiveMissCount > 0)
speedPartValue *= calculateMissPenalty(effectiveMissCount, attributes.SpeedDifficultStrainCount);
// Scale the speed value with accuracy and OD. // Scale the speed value with accuracy and OD.
speedPartValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); speedPartValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
@ -365,11 +366,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double lengthBonus = CalculateDefaultLengthBonus(totalHits); double lengthBonus = CalculateDefaultLengthBonus(totalHits);
hiddenValue *= lengthBonus; hiddenValue *= lengthBonus;
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0) if (effectiveMissCount > 0)
hiddenValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); hiddenValue *= calculateMissPenalty(effectiveMissCount, attributes.HiddenDifficultStrainCount);
hiddenValue *= getComboScalingFactor(attributes);
// Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework. // Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework.
hiddenValue *= accuracy * accuracy; hiddenValue *= accuracy * accuracy;
@ -442,13 +440,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return 1 + result; return 1 + result;
} }
// Miss penalty assumes that a player will miss on the hardest parts of a map,
// so we use the amount of relatively difficult sections to adjust miss penalty
// to make it more punishing on maps with lower amount of hard sections.
private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.96 / ((missCount / (4 * Math.Pow(Math.Log(difficultStrainCount), 0.94))) + 1);
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
private int totalHits => countGreat + countOk + countMeh + countMiss;
// Limits reading difficulty by the difficulty of full-memorisation (assumed to be mechanicalPerformance + flashlightPerformance + 25) // Limits reading difficulty by the difficulty of full-memorisation (assumed to be mechanicalPerformance + flashlightPerformance + 25)
// Desmos graph assuming that x = cognitionPerformance, while y = mechanicalPerformance + flaslightPerformance // Desmos graph assuming that x = cognitionPerformance, while y = mechanicalPerformance + flaslightPerformance
// https://www.desmos.com/3d/vjygrxtkqs // https://www.desmos.com/3d/vjygrxtkqs
@ -464,7 +455,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return ratio * capPerformance; return ratio * capPerformance;
} }
// Miss penalty assumes that a player will miss on the hardest parts of a map,
// so we use the amount of relatively difficult sections to adjust miss penalty
// to make it more punishing on maps with lower amount of hard sections.
private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.96 / ((missCount / (4 * Math.Pow(Math.Log(difficultStrainCount), 0.94))) + 1);
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
private static double softmin(double a, double b, double power = Math.E) => a * b / Math.Log(Math.Pow(power, a) + Math.Pow(power, b), power); private static double softmin(double a, double b, double power = Math.E) => a * b / Math.Log(Math.Pow(power, a) + Math.Pow(power, b), power);
private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); private static double logistic(double x) => 1 / (1 + Math.Exp(-x));
private int totalHits => countGreat + countOk + countMeh + countMiss;
} }
} }

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{ {
CurrentStrain *= StrainDecay(current.DeltaTime); CurrentStrain *= StrainDecay(current.DeltaTime);
CurrentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * SkillMultiplier; CurrentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * SkillMultiplier;
ObjectStrains.Add(currentStrain); ObjectStrains.Add(CurrentStrain);
return CurrentStrain; return CurrentStrain;
} }

View File

@ -26,8 +26,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected virtual double StrainDecayBase => 0.15; protected virtual double StrainDecayBase => 0.15;
protected double StrainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); protected double StrainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
protected List<double> ObjectStrains = new List<double>();
protected double Difficulty;
protected OsuStrainSkill(Mod[] mods) protected OsuStrainSkill(Mod[] mods)
: base(mods) : base(mods)
@ -63,20 +61,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
return Difficulty; return Difficulty;
} }
/// <summary>
/// Returns the number of strains weighted against the top strain.
/// The result is scaled by clock rate as it affects the total number of strains.
/// </summary>
public double CountDifficultStrains()
{
if (Difficulty == 0)
return 0.0;
double consistentTopStrain = Difficulty / 10; // What would the top strain be if all strain values were identical
// Use a weighted sum of all strains. Constants are arbitrary and give nice values
return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88))));
}
/// <summary> /// <summary>
/// Converts difficulty value from <see cref="OsuDifficultyAttributes"/> to base performance. /// Converts difficulty value from <see cref="OsuDifficultyAttributes"/> to base performance.
/// </summary> /// </summary>

View File

@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{ {
public class ReadingLowAR : GraphSkill public class ReadingLowAR : GraphSkill
{ {
private readonly List<double> difficulties = new List<double>();
private double skillMultiplier => 1.23; private double skillMultiplier => 1.23;
private double aimComponentMultiplier => 0.4; private double aimComponentMultiplier => 0.4;
@ -40,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double totalDensityDifficulty = (currentDensityAimStrain + densityReadingDifficulty) * skillMultiplier; double totalDensityDifficulty = (currentDensityAimStrain + densityReadingDifficulty) * skillMultiplier;
difficulties.Add(totalDensityDifficulty); ObjectStrains.Add(totalDensityDifficulty);
if (current.Index == 0) if (current.Index == 0)
CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
@ -59,11 +58,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private double reducedNoteBaseline => 0.7; private double reducedNoteBaseline => 0.7;
public override double DifficultyValue() public override double DifficultyValue()
{ {
double difficulty = 0;
// Sections with 0 difficulty are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871). // Sections with 0 difficulty are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
// These sections will not contribute to the difficulty. // These sections will not contribute to the difficulty.
var peaks = difficulties.Where(p => p > 0); var peaks = ObjectStrains.Where(p => p > 0);
List<double> values = peaks.OrderByDescending(d => d).ToList(); List<double> values = peaks.OrderByDescending(d => d).ToList();
@ -75,14 +72,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
values = values.OrderByDescending(d => d).ToList(); values = values.OrderByDescending(d => d).ToList();
Difficulty = 0;
// Difficulty is the weighted sum of the highest strains from every section. // Difficulty is the weighted sum of the highest strains from every section.
// We're sorting from highest to lowest strain. // We're sorting from highest to lowest strain.
for (int i = 0; i < values.Count; i++) for (int i = 0; i < values.Count; i++)
{ {
difficulty += values[i] / (i + 1); Difficulty += values[i] / (i + 1);
} }
return difficulty; return Difficulty;
} }
public static double DifficultyToPerformance(double difficulty) => Math.Max( public static double DifficultyToPerformance(double difficulty) => Math.Max(
Math.Max(Math.Pow(difficulty, 1.5) * 20, Math.Pow(difficulty, 2) * 17.0), Math.Max(Math.Pow(difficulty, 1.5) * 20, Math.Pow(difficulty, 2) * 17.0),
@ -95,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
: base(mods, false) : base(mods, false)
{ {
} }
protected new double SkillMultiplier => 7.2; protected new double SkillMultiplier => 7.632;
protected override double StrainValueAt(DifficultyHitObject current) protected override double StrainValueAt(DifficultyHitObject current)
{ {
@ -107,6 +106,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
hiddenDifficulty *= SkillMultiplier; hiddenDifficulty *= SkillMultiplier;
CurrentStrain += hiddenDifficulty; CurrentStrain += hiddenDifficulty;
ObjectStrains.Add(CurrentStrain);
return CurrentStrain; return CurrentStrain;
} }
@ -204,8 +204,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double adjustedDifficulty = performanceToDifficulty(totalPerformance); double adjustedDifficulty = performanceToDifficulty(totalPerformance);
double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0);
Difficulty = skill_multiplier * Math.Pow(difficultyValue, MECHANICAL_PP_POWER);
// Sqrt value to make difficulty depend less on mechanical difficulty // Sqrt value to make difficulty depend less on mechanical difficulty
return skill_multiplier * Math.Pow(difficultyValue, MECHANICAL_PP_POWER); return Difficulty;
} }
public class HighARAimComponent : Aim public class HighARAimComponent : Aim
{ {

View File

@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -27,6 +29,9 @@ namespace osu.Game.Rulesets.Difficulty.Skills
this.mods = mods; this.mods = mods;
} }
protected List<double> ObjectStrains = new List<double>();
protected double Difficulty;
/// <summary> /// <summary>
/// Process a <see cref="DifficultyHitObject"/>. /// Process a <see cref="DifficultyHitObject"/>.
/// </summary> /// </summary>
@ -37,5 +42,19 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// Returns the calculated difficulty value representing all <see cref="DifficultyHitObject"/>s that have been processed up to this point. /// Returns the calculated difficulty value representing all <see cref="DifficultyHitObject"/>s that have been processed up to this point.
/// </summary> /// </summary>
public abstract double DifficultyValue(); public abstract double DifficultyValue();
/// <summary>
/// Returns the number of strains weighted against the top strain.
/// The result is scaled by clock rate as it affects the total number of strains.
/// </summary>
public double CountDifficultStrains()
{
if (Difficulty == 0)
return 0.0;
double consistentTopStrain = Difficulty / 10; // What would the top strain be if all strain values were identical
// Use a weighted sum of all strains. Constants are arbitrary and give nice values
return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88))));
}
} }
} }