From e72ae6d9aeccc5a90d00b47f2e3cdd7046c35905 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 7 Oct 2024 23:32:00 +0300 Subject: [PATCH] ported CSR correctly --- .../Difficulty/OsuDifficultyAttributes.cs | 6 +++ .../Difficulty/OsuDifficultyCalculator.cs | 12 ++++-- .../Difficulty/OsuPerformanceCalculator.cs | 41 ++++++++++--------- .../Difficulty/Skills/Aim.cs | 2 +- .../Difficulty/Skills/OsuStrainSkill.cs | 16 -------- .../Difficulty/Skills/Reading.cs | 20 +++++---- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 19 +++++++++ 7 files changed, 67 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index a4b6668f7b..72168b00f3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -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; } + /// /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc). /// diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 7d1899bb93..612b477add 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -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; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 9cc6f88ae1..a0f98216a8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -216,6 +216,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (!usingClassicSliderAccuracy) amountHitObjectsWithAccuracy += attributes.SliderCount; + if (score.Mods.OfType().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; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index f8359f87dd..35e7162de3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -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; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index fc08e01f0e..d77952fbbe 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -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 ObjectStrains = new List(); - protected double Difficulty; protected OsuStrainSkill(Mod[] mods) : base(mods) @@ -63,20 +61,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return Difficulty; } - /// - /// 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. - /// - 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)))); - } - /// /// Converts difficulty value from to base performance. /// diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index e2c5597aad..7f80999eb1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { public class ReadingLowAR : GraphSkill { - private readonly List difficulties = new List(); 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 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 { diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 8b8892113b..4f81183b1f 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . 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 ObjectStrains = new List(); + protected double Difficulty; + /// /// Process a . /// @@ -37,5 +42,19 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// Returns the calculated difficulty value representing all s that have been processed up to this point. /// public abstract double DifficultyValue(); + + /// + /// 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. + /// + 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)))); + } } }