1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-12 17:23:09 +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")]
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>
/// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>

View File

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

View File

@ -216,6 +216,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (!usingClassicSliderAccuracy)
amountHitObjectsWithAccuracy += attributes.SliderCount;
if (score.Mods.OfType<OsuModClassic>().All(m => !m.NoSliderHeadAccuracy.Value))
amountHitObjectsWithAccuracy += attributes.SliderCount;
if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
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.
if (effectiveMissCount > 0)
readingValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
readingValue *= getComboScalingFactor(attributes);
readingValue *= calculateMissPenalty(effectiveMissCount, attributes.LowArDifficultStrainCount);
// Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework.
readingValue *= accuracy * accuracy;
@ -305,12 +306,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
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
double aimPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty);
double speedPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
@ -330,6 +325,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimPartValue *= sliderNerfFactor;
}
if (effectiveMissCount > 0)
aimPartValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount);
aimPartValue *= accuracy;
// It is important to consider accuracy difficulty when scaling with accuracy.
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 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.
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);
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)
hiddenValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
hiddenValue *= getComboScalingFactor(attributes);
hiddenValue *= calculateMissPenalty(effectiveMissCount, attributes.HiddenDifficultStrainCount);
// Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework.
hiddenValue *= accuracy * accuracy;
@ -442,13 +440,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
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)
// Desmos graph assuming that x = cognitionPerformance, while y = mechanicalPerformance + flaslightPerformance
// https://www.desmos.com/3d/vjygrxtkqs
@ -464,7 +455,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty
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 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 += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * SkillMultiplier;
ObjectStrains.Add(currentStrain);
ObjectStrains.Add(CurrentStrain);
return CurrentStrain;
}

View File

@ -26,8 +26,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected virtual double StrainDecayBase => 0.15;
protected double StrainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
protected List<double> ObjectStrains = new List<double>();
protected double Difficulty;
protected OsuStrainSkill(Mod[] mods)
: base(mods)
@ -63,20 +61,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
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>
/// Converts difficulty value from <see cref="OsuDifficultyAttributes"/> to base performance.
/// </summary>

View File

@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
public class ReadingLowAR : GraphSkill
{
private readonly List<double> difficulties = new List<double>();
private double skillMultiplier => 1.23;
private double aimComponentMultiplier => 0.4;
@ -40,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double totalDensityDifficulty = (currentDensityAimStrain + densityReadingDifficulty) * skillMultiplier;
difficulties.Add(totalDensityDifficulty);
ObjectStrains.Add(totalDensityDifficulty);
if (current.Index == 0)
CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
@ -59,11 +58,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private double reducedNoteBaseline => 0.7;
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).
// 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();
@ -75,14 +72,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
values = values.OrderByDescending(d => d).ToList();
Difficulty = 0;
// Difficulty is the weighted sum of the highest strains from every section.
// We're sorting from highest to lowest strain.
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(
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)
{
}
protected new double SkillMultiplier => 7.2;
protected new double SkillMultiplier => 7.632;
protected override double StrainValueAt(DifficultyHitObject current)
{
@ -107,6 +106,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
hiddenDifficulty *= SkillMultiplier;
CurrentStrain += hiddenDifficulty;
ObjectStrains.Add(CurrentStrain);
return CurrentStrain;
}
@ -204,8 +204,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double adjustedDifficulty = performanceToDifficulty(totalPerformance);
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
return skill_multiplier * Math.Pow(difficultyValue, MECHANICAL_PP_POWER);
return Difficulty;
}
public class HighARAimComponent : Aim
{

View File

@ -1,7 +1,9 @@
// 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.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
@ -27,6 +29,9 @@ namespace osu.Game.Rulesets.Difficulty.Skills
this.mods = mods;
}
protected List<double> ObjectStrains = new List<double>();
protected double Difficulty;
/// <summary>
/// Process a <see cref="DifficultyHitObject"/>.
/// </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.
/// </summary>
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))));
}
}
}