From b95d69bbf916d7c293eea8fef88c6eb7b960b435 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 21 Jun 2022 08:54:27 +0100 Subject: [PATCH 01/96] initial --- .../Evaluators/CognitionEvaluator.cs | 56 +++++++++++++++++++ .../Difficulty/OsuDifficultyAttributes.cs | 6 ++ .../Difficulty/OsuDifficultyCalculator.cs | 10 +++- .../Difficulty/OsuPerformanceAttributes.cs | 4 ++ .../Difficulty/OsuPerformanceCalculator.cs | 54 +++++++++--------- .../Preprocessing/OsuDifficultyHitObject.cs | 2 + .../Difficulty/Skills/Cognition.cs | 41 ++++++++++++++ 7 files changed, 144 insertions(+), 29 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs create mode 100644 osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs new file mode 100644 index 000000000000..f2f4692a80dd --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -0,0 +1,56 @@ +// 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 osu.Framework.Extensions.ObjectExtensions; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; + +namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators +{ + public static class CognitionEvaluator + { + public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) + { + var currObj = (OsuDifficultyHitObject)current; + double noteDensity = 1.0; + double lastOpacity = 1.0; + int forwardsSearch = 0; + + double difficulty = 0.0; + + // This loop sucks so much lol. + // Will be replaced in conjuction with the "objects with current visible" and the "currently visible objects" lists + while (lastOpacity > 0) + { + if (currObj.Next(forwardsSearch).IsNull()) + break; + + var searchObject = (OsuDifficultyHitObject)currObj.Next(forwardsSearch); + + lastOpacity = searchObject.OpacityAt(currObj.BaseObject.StartTime, false) + * logistic((searchObject.MinimumJumpDistance - 100) / 15); + + noteDensity += lastOpacity; + forwardsSearch++; + } + + double noteDensityDifficulty = noteDensity; + + if (hidden) + noteDensityDifficulty = Math.Pow(noteDensity, 2.5) * 1.5; + + difficulty += noteDensityDifficulty; + + double preemptDifficulty = 0.0; + if (currObj.preempt < 400) + preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / 7; + + difficulty += preemptDifficulty; + + return difficulty; + } + + private static double logistic(double x) => 1 / (1 + Math.Pow(Math.E, -x)); + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 3deed4ea3ded..4e772394b719 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -30,6 +30,12 @@ public class OsuDifficultyAttributes : DifficultyAttributes [JsonProperty("flashlight_difficulty")] public double FlashlightDifficulty { get; set; } + /// + /// The difficulty corresponding to the cognition skill. + /// + [JsonProperty("cognition_difficulty")] + public double CognitionDifficulty { get; set; } + /// /// Describes how much of is contributed to by hitcircles or sliders. /// A value closer to 1.0 indicates most of is contributed by hitcircles. diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 0fa69c180a7c..d6d743de3cf1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -37,6 +37,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; + double cognitionRating = Math.Sqrt(skills[4].DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; @@ -50,11 +51,14 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat if (mods.Any(h => h is OsuModFlashlight)) baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; + double baseCognitionPerformance = Math.Pow(5 * Math.Max(1, cognitionRating / 0.0675) - 4, 3) / 100000; + double basePerformance = Math.Pow( Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1) + - Math.Pow(baseFlashlightPerformance, 1.1), 1.0 / 1.1 + Math.Pow(baseFlashlightPerformance, 1.1) + + Math.Pow(baseCognitionPerformance, 1.1), 1.0 / 1.1 ); double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; @@ -74,6 +78,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat AimDifficulty = aimRating, SpeedDifficulty = speedRating, FlashlightDifficulty = flashlightRating, + CognitionDifficulty = cognitionRating, SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, @@ -112,7 +117,8 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo new Aim(mods, true), new Aim(mods, false), new Speed(mods, hitWindowGreat), - new Flashlight(mods) + new Flashlight(mods), + new Cognition(mods) }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 0aeaf7669f68..d58b1ea81648 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -21,6 +21,9 @@ public class OsuPerformanceAttributes : PerformanceAttributes [JsonProperty("flashlight")] public double Flashlight { get; set; } + [JsonProperty("cognition")] + public double Cognition { get; set; } + [JsonProperty("effective_miss_count")] public double EffectiveMissCount { get; set; } @@ -33,6 +36,7 @@ public override IEnumerable GetAttributesForDisplay yield return new PerformanceDisplayAttribute(nameof(Speed), "Speed", Speed); yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); yield return new PerformanceDisplayAttribute(nameof(Flashlight), "Flashlight Bonus", Flashlight); + yield return new PerformanceDisplayAttribute(nameof(Cognition), "Cognition", Cognition); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d046be9ccbb0..0282e6598e17 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -59,12 +59,14 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double speedValue = computeSpeedValue(score, osuAttributes); double accuracyValue = computeAccuracyValue(score, osuAttributes); double flashlightValue = computeFlashlightValue(score, osuAttributes); + double cognitionValue = computeCognitionValue(score, osuAttributes); double totalValue = Math.Pow( Math.Pow(aimValue, 1.1) + Math.Pow(speedValue, 1.1) + Math.Pow(accuracyValue, 1.1) + - Math.Pow(flashlightValue, 1.1), 1.0 / 1.1 + Math.Pow(flashlightValue, 1.1) + + Math.Pow(cognitionValue, 1.1), 1.0 / 1.1 ) * multiplier; return new OsuPerformanceAttributes @@ -73,6 +75,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s Speed = speedValue, Accuracy = accuracyValue, Flashlight = flashlightValue, + Cognition = cognitionValue, EffectiveMissCount = effectiveMissCount, Total = totalValue }; @@ -97,21 +100,8 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut aimValue *= getComboScalingFactor(attributes); - double approachRateFactor = 0.0; - if (attributes.ApproachRate > 10.33) - approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); - else if (attributes.ApproachRate < 8.0) - approachRateFactor = 0.1 * (8.0 - attributes.ApproachRate); - - aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. - if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); - else if (score.Mods.Any(h => h is OsuModHidden)) - { - // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); - } // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = attributes.SliderCount * 0.15; @@ -144,22 +134,11 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib speedValue *= getComboScalingFactor(attributes); - double approachRateFactor = 0.0; - if (attributes.ApproachRate > 10.33) - approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); - - speedValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. - if (score.Mods.Any(m => m is OsuModBlinds)) { // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. speedValue *= 1.12; } - else if (score.Mods.Any(m => m is OsuModHidden)) - { - // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); - } // Scale the speed value with accuracy and OD. speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); @@ -198,8 +177,6 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; - else if (score.Mods.Any(m => m is OsuModHidden)) - accuracyValue *= 1.08; if (score.Mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; @@ -237,6 +214,29 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a return flashlightValue; } + private double computeCognitionValue(ScoreInfo score, OsuDifficultyAttributes attributes) + { + double rawCognition = attributes.CognitionDifficulty; + + if (score.Mods.Any(m => m is OsuModTouchDevice)) + rawCognition = Math.Pow(rawCognition, 0.8); + + double cognitionValue = Math.Pow(rawCognition, 2.0) * 25.0; + + // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. + if (effectiveMissCount > 0) + cognitionValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); + + cognitionValue *= getComboScalingFactor(attributes); + + // Scale the flashlight value with accuracy _harshly_. + cognitionValue *= accuracy * accuracy; + // It is important to also consider accuracy difficulty when doing that. + cognitionValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; + + return cognitionValue; + } + private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) { // Guess the number of misses + slider breaks from combo diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 20921dd282ae..ef53fb6b816b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -74,12 +74,14 @@ public class OsuDifficultyHitObject : DifficultyHitObject private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; + public readonly double preempt; public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, List objects, int index) : base(hitObject, lastObject, clockRate, objects, index) { this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; + preempt = BaseObject.TimePreempt / clockRate; // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. StrainTime = Math.Max(DeltaTime, min_delta_time); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs new file mode 100644 index 000000000000..bdba68f3d8c1 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs @@ -0,0 +1,41 @@ +// 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.Difficulty.Skills; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Difficulty.Evaluators; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Difficulty.Skills +{ + public class Cognition : Skill + { + private readonly List difficulties = new List(); + private readonly bool hasHiddenMod; + private const double skill_multiplier = 34; + + public Cognition(Mod[] mods) + : base(mods) + { + hasHiddenMod = mods.Any(m => m is OsuModHidden); + } + + public override void Process(DifficultyHitObject current) => difficulties.Add(CognitionEvaluator.EvaluateDifficultyOf(current, hasHiddenMod) * skill_multiplier); + + public override double DifficultyValue() + { + double difficulty = 0; + + for (int i = 0; i < difficulties.Count; i++) + difficulty += difficulties[i] * weight(i); + + return Math.Pow(difficulty, 0.5); + } + + private double weight(int x) => x / (x + 200.0); + } +} From ee8f207affa73cd012a4f3c4ae8c5ac64d039dd4 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:15:31 +0100 Subject: [PATCH 02/96] Make changes which I forgot lol --- .../Evaluators/CognitionEvaluator.cs | 33 ++++++++++++------- .../Difficulty/OsuPerformanceCalculator.cs | 2 +- .../Difficulty/Skills/Cognition.cs | 6 ++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index f2f4692a80dd..b200c90b4c2b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -5,6 +5,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { @@ -12,33 +13,43 @@ public static class CognitionEvaluator { public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) { + if (current.BaseObject is Spinner) + return 0; + var currObj = (OsuDifficultyHitObject)current; double noteDensity = 1.0; - double lastOpacity = 1.0; - int forwardsSearch = 0; double difficulty = 0.0; // This loop sucks so much lol. // Will be replaced in conjuction with the "objects with current visible" and the "currently visible objects" lists - while (lastOpacity > 0) + for (int i = 0; i < 100; i++) { - if (currObj.Next(forwardsSearch).IsNull()) + if (currObj.Next(i + 1).IsNull()) + break; + + var currLoopObj = (OsuDifficultyHitObject)currObj.Next(i); + var nextLoopObj = (OsuDifficultyHitObject)currObj.Next(i + 1); + + double opacity = currLoopObj.OpacityAt(currObj.BaseObject.StartTime, false); + + if (opacity == 0) break; - var searchObject = (OsuDifficultyHitObject)currObj.Next(forwardsSearch); + // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. + opacity *= logistic((currLoopObj.MinimumJumpDistance - 100) / 15); - lastOpacity = searchObject.OpacityAt(currObj.BaseObject.StartTime, false) - * logistic((searchObject.MinimumJumpDistance - 100) / 15); + // Objects that are arranged in a mostly-linear fashion should be easy to read (such as circles in a stream). + if (nextLoopObj.Angle.IsNotNull()) + opacity *= 1 - Math.Pow(Math.Sin(0.5 * nextLoopObj.Angle.Value), 5); - noteDensity += lastOpacity; - forwardsSearch++; + noteDensity += opacity; } - double noteDensityDifficulty = noteDensity; + double noteDensityDifficulty = 0; if (hidden) - noteDensityDifficulty = Math.Pow(noteDensity, 2.5) * 1.5; + noteDensityDifficulty = Math.Pow(noteDensity, 2.5) * 1.2; difficulty += noteDensityDifficulty; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 0282e6598e17..afcc69643cdd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -229,7 +229,7 @@ private double computeCognitionValue(ScoreInfo score, OsuDifficultyAttributes at cognitionValue *= getComboScalingFactor(attributes); - // Scale the flashlight value with accuracy _harshly_. + // Scale the cognition value with accuracy _harshly_. cognitionValue *= accuracy * accuracy; // It is important to also consider accuracy difficulty when doing that. cognitionValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs index bdba68f3d8c1..b42b6aa053a3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs @@ -16,7 +16,7 @@ public class Cognition : Skill { private readonly List difficulties = new List(); private readonly bool hasHiddenMod; - private const double skill_multiplier = 34; + private const double skill_multiplier = 17000; public Cognition(Mod[] mods) : base(mods) @@ -33,9 +33,9 @@ public override double DifficultyValue() for (int i = 0; i < difficulties.Count; i++) difficulty += difficulties[i] * weight(i); - return Math.Pow(difficulty, 0.5); + return Math.Pow(difficulty, 1.0 / 3.0); } - private double weight(int x) => x / (x + 200.0); + private double weight(int x) => x / (x + 100.0); } } From ec543c1c48c6a9a1ed9c8aea3f212c270afd12a7 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:16:45 +0100 Subject: [PATCH 03/96] Clarify that variable names currently suck --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index b200c90b4c2b..e68eb7a3e5c1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -23,6 +23,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // This loop sucks so much lol. // Will be replaced in conjuction with the "objects with current visible" and the "currently visible objects" lists + // Also variable names like opacity and note density don't seem accurate anymore :face_with_monocole:... for (int i = 0; i < 100; i++) { if (currObj.Next(i + 1).IsNull()) From d8f18661d3af71ec429a2d70c5cb6d4e1e83b071 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 21 Jun 2022 23:21:57 +0100 Subject: [PATCH 04/96] Wave of changes --- .../Evaluators/CognitionEvaluator.cs | 22 +++++++++++++------ .../Difficulty/Skills/Cognition.cs | 21 ++++++++++++------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index e68eb7a3e5c1..83daad838342 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -17,9 +17,9 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd return 0; var currObj = (OsuDifficultyHitObject)current; - double noteDensity = 1.0; + double currVelocity = currObj.LazyJumpDistance / currObj.StrainTime; - double difficulty = 0.0; + double noteDensity = 1.0; // This loop sucks so much lol. // Will be replaced in conjuction with the "objects with current visible" and the "currently visible objects" lists @@ -47,18 +47,26 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd noteDensity += opacity; } - double noteDensityDifficulty = 0; + double noteDensityDifficulty = Math.Pow(Math.Max(0, noteDensity - 3), 2.5); + + double hiddenDifficulty = 0; if (hidden) - noteDensityDifficulty = Math.Pow(noteDensity, 2.5) * 1.2; + { + noteDensityDifficulty *= 3.2; - difficulty += noteDensityDifficulty; + // Really not sure about this, but without this a lot of normal HD plays become underweight. + hiddenDifficulty = 11 * currObj.LazyJumpDistance / currObj.StrainTime; + } double preemptDifficulty = 0.0; if (currObj.preempt < 400) - preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / 7; + preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / 14; + + // Buff rhythm on high AR. + preemptDifficulty *= RhythmEvaluator.EvaluateDifficultyOf(current, 30); - difficulty += preemptDifficulty; + double difficulty = Math.Max(preemptDifficulty, hiddenDifficulty) + noteDensityDifficulty; return difficulty; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs index b42b6aa053a3..2b59fbf1eef5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs @@ -1,7 +1,6 @@ // 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; @@ -16,7 +15,7 @@ public class Cognition : Skill { private readonly List difficulties = new List(); private readonly bool hasHiddenMod; - private const double skill_multiplier = 17000; + private const double skill_multiplier = 2.4; public Cognition(Mod[] mods) : base(mods) @@ -30,12 +29,20 @@ public override double DifficultyValue() { double difficulty = 0; - for (int i = 0; i < difficulties.Count; i++) - difficulty += difficulties[i] * weight(i); + // 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); - return Math.Pow(difficulty, 1.0 / 3.0); - } + List values = peaks.OrderByDescending(d => d).ToList(); + + // 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); + } - private double weight(int x) => x / (x + 100.0); + return difficulty; + } } } From d83f1c425305fb8852b6ed29c2c76488f8f89c2c Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Fri, 24 Jun 2022 15:48:50 +0100 Subject: [PATCH 05/96] Adjust note-density and hidden buffs --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 83daad838342..8686addbc46e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -47,7 +47,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd noteDensity += opacity; } - double noteDensityDifficulty = Math.Pow(Math.Max(0, noteDensity - 3), 2.5); + double noteDensityDifficulty = Math.Pow(Math.Max(0, noteDensity - 2), 2); double hiddenDifficulty = 0; @@ -56,7 +56,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd noteDensityDifficulty *= 3.2; // Really not sure about this, but without this a lot of normal HD plays become underweight. - hiddenDifficulty = 11 * currObj.LazyJumpDistance / currObj.StrainTime; + hiddenDifficulty = 7 * currObj.LazyJumpDistance / currObj.StrainTime; } double preemptDifficulty = 0.0; From 06b14250a6a0d4cd6bd58e120bd91d0ea1bcd501 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 19 Jul 2022 19:17:16 +0100 Subject: [PATCH 06/96] WIP --- .../Evaluators/CognitionEvaluator.cs | 76 +++++++++++++------ .../Preprocessing/DifficultyHitObject.cs | 5 ++ 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 8686addbc46e..b5cabb06d82b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Extensions.ObjectExtensions; +using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -19,32 +19,22 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd var currObj = (OsuDifficultyHitObject)current; double currVelocity = currObj.LazyJumpDistance / currObj.StrainTime; - double noteDensity = 1.0; - - // This loop sucks so much lol. - // Will be replaced in conjuction with the "objects with current visible" and the "currently visible objects" lists - // Also variable names like opacity and note density don't seem accurate anymore :face_with_monocole:... - for (int i = 0; i < 100; i++) - { - if (currObj.Next(i + 1).IsNull()) - break; - - var currLoopObj = (OsuDifficultyHitObject)currObj.Next(i); - var nextLoopObj = (OsuDifficultyHitObject)currObj.Next(i + 1); - - double opacity = currLoopObj.OpacityAt(currObj.BaseObject.StartTime, false); - - if (opacity == 0) - break; + List pastVisibleObjects = retrievePastVisibleObjects(currObj); + List currentVisibleObjects = retrieveCurrentVisibleObjects(currObj); - // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - opacity *= logistic((currLoopObj.MinimumJumpDistance - 100) / 15); + // Rather than note density being the number of on-screen objects visible at the current object, + // consider it as how many objects the current object has been visible for. + double noteDensity = 1.0; - // Objects that are arranged in a mostly-linear fashion should be easy to read (such as circles in a stream). - if (nextLoopObj.Angle.IsNotNull()) - opacity *= 1 - Math.Pow(Math.Sin(0.5 * nextLoopObj.Angle.Value), 5); + double loopOpacity = 1.0; + int previousIndex = 0; - noteDensity += opacity; + while (loopOpacity > 0) + { + var loopObj = (OsuDifficultyHitObject)currObj.Previous(previousIndex); + loopOpacity = currObj.OpacityAt(loopObj.StartTime, false); + noteDensity += loopOpacity; + previousIndex++; } double noteDensityDifficulty = Math.Pow(Math.Max(0, noteDensity - 2), 2); @@ -71,6 +61,44 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd return difficulty; } + // Returns a list of objects that are visible on screen at + // the point in time at which the current object becomes visible. + private static List retrievePastVisibleObjects(OsuDifficultyHitObject current) + { + List objects = new List(); + + for (int i = 0; i < current.Index; i++) + { + DifficultyHitObject currentObj = current.Previous(i); + + if (current.OpacityAt(currentObj.StartTime, false) <= 0) + break; + + objects.Add(currentObj); + } + + return objects; + } + + // Returns a list of objects that are visible on screen at + // the point in time at which the current object needs is clicked. + private static List retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) + { + List objects = new List(); + + for (int i = 0; i < current.Count; i++) + { + OsuDifficultyHitObject currentObj = (OsuDifficultyHitObject)current.Next(i); + + if (currentObj.OpacityAt(current.StartTime, false) <= 0) + break; + + objects.Add(currentObj); + } + + return objects; + } + private static double logistic(double x) => 1 / (1 + Math.Pow(Math.E, -x)); } } diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index 9ce0906dea14..3da9c5ef0a3e 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -16,6 +16,11 @@ public class DifficultyHitObject { private readonly IReadOnlyList difficultyHitObjects; + /// + /// The index of this in the list of all s. + /// + public int Count => difficultyHitObjects.Count; + /// /// The index of this in the list of all s. /// From 3c38c9e2a700dadcd5f405aa2c0e312de88cb679 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 9 Aug 2022 13:43:12 +0100 Subject: [PATCH 07/96] Fix null objects --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index b5cabb06d82b..66473cad28fe 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -13,7 +14,7 @@ public static class CognitionEvaluator { public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) { - if (current.BaseObject is Spinner) + if (current.BaseObject is Spinner || current.Index == 0) return 0; var currObj = (OsuDifficultyHitObject)current; @@ -32,7 +33,15 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd while (loopOpacity > 0) { var loopObj = (OsuDifficultyHitObject)currObj.Previous(previousIndex); + + if (loopObj.IsNull()) + break; + loopOpacity = currObj.OpacityAt(loopObj.StartTime, false); + + if (loopOpacity <= 0) + break; + noteDensity += loopOpacity; previousIndex++; } @@ -90,7 +99,7 @@ private static List retrieveCurrentVisibleObjects(OsuDiffic { OsuDifficultyHitObject currentObj = (OsuDifficultyHitObject)current.Next(i); - if (currentObj.OpacityAt(current.StartTime, false) <= 0) + if (currentObj.IsNull() || currentObj.OpacityAt(current.StartTime, false) <= 0) break; objects.Add(currentObj); From e6193e8b98cd95b8c7136f480a6d4c45957a7609 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 10 Aug 2022 19:09:42 +0100 Subject: [PATCH 08/96] Use past objects for note density --- .../Evaluators/CognitionEvaluator.cs | 60 ++++++++----------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 66473cad28fe..4f2f5b05f87d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using FFmpeg.AutoGen; using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; @@ -12,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { public static class CognitionEvaluator { + private const double cognition_window_size = 2000; + public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) { if (current.BaseObject is Spinner || current.Index == 0) @@ -20,44 +23,33 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd var currObj = (OsuDifficultyHitObject)current; double currVelocity = currObj.LazyJumpDistance / currObj.StrainTime; - List pastVisibleObjects = retrievePastVisibleObjects(currObj); - List currentVisibleObjects = retrieveCurrentVisibleObjects(currObj); + List pastVisibleObjects = retrievePastVisibleObjects(currObj); + List currentVisibleObjects = retrieveCurrentVisibleObjects(currObj); // Rather than note density being the number of on-screen objects visible at the current object, // consider it as how many objects the current object has been visible for. - double noteDensity = 1.0; - - double loopOpacity = 1.0; - int previousIndex = 0; + double noteDensityDifficulty = 1.0; - while (loopOpacity > 0) + foreach (var loopObj in pastVisibleObjects) { - var loopObj = (OsuDifficultyHitObject)currObj.Previous(previousIndex); + var prevLoopObj = (OsuDifficultyHitObject)loopObj.Previous(0); - if (loopObj.IsNull()) - break; + double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); - loopOpacity = currObj.OpacityAt(loopObj.StartTime, false); + // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. + loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 125) / 15); - if (loopOpacity <= 0) - break; + // Objects that are arranged in a mostly-linear fashion should be easy to read (such as circles in a stream). + //if (loopObj.Angle.IsNotNull() && prevLoopObj.Angle.IsNotNull()) + // loopDifficulty *= 1 - Math.Pow(Math.Sin(0.5 * loopObj.Angle.Value), 5); - noteDensity += loopOpacity; - previousIndex++; + noteDensityDifficulty += loopDifficulty; } - double noteDensityDifficulty = Math.Pow(Math.Max(0, noteDensity - 2), 2); + noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, noteDensityDifficulty - 1)), 2.3); double hiddenDifficulty = 0; - if (hidden) - { - noteDensityDifficulty *= 3.2; - - // Really not sure about this, but without this a lot of normal HD plays become underweight. - hiddenDifficulty = 7 * currObj.LazyJumpDistance / currObj.StrainTime; - } - double preemptDifficulty = 0.0; if (currObj.preempt < 400) preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / 14; @@ -72,18 +64,18 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Returns a list of objects that are visible on screen at // the point in time at which the current object becomes visible. - private static List retrievePastVisibleObjects(OsuDifficultyHitObject current) + private static List retrievePastVisibleObjects(OsuDifficultyHitObject current) { - List objects = new List(); + List objects = new List(); for (int i = 0; i < current.Index; i++) { - DifficultyHitObject currentObj = current.Previous(i); + OsuDifficultyHitObject loopObj = (OsuDifficultyHitObject)current.Previous(i); - if (current.OpacityAt(currentObj.StartTime, false) <= 0) + if (loopObj.IsNull() || current.StartTime - loopObj.StartTime > cognition_window_size) break; - objects.Add(currentObj); + objects.Add(loopObj); } return objects; @@ -91,18 +83,18 @@ private static List retrievePastVisibleObjects(OsuDifficult // Returns a list of objects that are visible on screen at // the point in time at which the current object needs is clicked. - private static List retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) + private static List retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) { - List objects = new List(); + List objects = new List(); for (int i = 0; i < current.Count; i++) { - OsuDifficultyHitObject currentObj = (OsuDifficultyHitObject)current.Next(i); + OsuDifficultyHitObject loopObj = (OsuDifficultyHitObject)current.Next(i); - if (currentObj.IsNull() || currentObj.OpacityAt(current.StartTime, false) <= 0) + if (loopObj.IsNull() || (loopObj.StartTime - current.StartTime) > cognition_window_size) break; - objects.Add(currentObj); + objects.Add(loopObj); } return objects; From 152a1955145d617c6d34a77900310d0fbf8ef4ab Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 10 Aug 2022 20:04:38 +0100 Subject: [PATCH 09/96] Add HD support --- .../Evaluators/CognitionEvaluator.cs | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 4f2f5b05f87d..b3b63bfd9c79 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators @@ -15,14 +16,21 @@ public static class CognitionEvaluator { private const double cognition_window_size = 2000; + private const double note_density_difficulty_multiplier = 1.0; + public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) { if (current.BaseObject is Spinner || current.Index == 0) return 0; var currObj = (OsuDifficultyHitObject)current; + var prevObj = (OsuDifficultyHitObject)current.Previous(0); + double currVelocity = currObj.LazyJumpDistance / currObj.StrainTime; + // Maybe I should just pass in clockrate... + var clockRateEstimate = current.BaseObject.StartTime / currObj.StartTime; + List pastVisibleObjects = retrievePastVisibleObjects(currObj); List currentVisibleObjects = retrieveCurrentVisibleObjects(currObj); @@ -30,6 +38,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // consider it as how many objects the current object has been visible for. double noteDensityDifficulty = 1.0; + double pastObjectDifficultyInfluence = 1.0; + foreach (var loopObj in pastVisibleObjects) { var prevLoopObj = (OsuDifficultyHitObject)loopObj.Previous(0); @@ -43,13 +53,24 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd //if (loopObj.Angle.IsNotNull() && prevLoopObj.Angle.IsNotNull()) // loopDifficulty *= 1 - Math.Pow(Math.Sin(0.5 * loopObj.Angle.Value), 5); - noteDensityDifficulty += loopDifficulty; + pastObjectDifficultyInfluence += loopDifficulty; } - noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, noteDensityDifficulty - 1)), 2.3); + noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 1)), 2.3) * note_density_difficulty_multiplier; double hiddenDifficulty = 0; + if (hidden) + { + var timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; + var isRhythmChange = (currObj.StrainTime - prevObj.StrainTime < 5); + + var timeDifficultyFactor = 1200 / pastObjectDifficultyInfluence; + + hiddenDifficulty += 12 * timeSpentInvisible / timeDifficultyFactor; + hiddenDifficulty += 7 * currVelocity; + } + double preemptDifficulty = 0.0; if (currObj.preempt < 400) preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / 14; @@ -100,6 +121,16 @@ private static List retrieveCurrentVisibleObjects(OsuDif return objects; } + private static double getDurationSpentInvisible(OsuDifficultyHitObject current) + { + var baseObject = (OsuHitObject)current.BaseObject; + + double fadeOutStartTime = baseObject.StartTime - baseObject.TimePreempt + baseObject.TimeFadeIn; + double fadeOutDuration = baseObject.TimePreempt * OsuModHidden.FADE_OUT_DURATION_MULTIPLIER; + + return (fadeOutStartTime + fadeOutDuration) - (baseObject.StartTime - baseObject.TimePreempt); + } + private static double logistic(double x) => 1 / (1 + Math.Pow(Math.E, -x)); } } From 2d5f2fc201ebd3ca73485b24716ee485a1f2156f Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 11 Aug 2022 00:33:44 +0100 Subject: [PATCH 10/96] Buff stream-spacing objects slightly, reduce velocity factor in HD --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index b3b63bfd9c79..6ad7a336673a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -47,7 +47,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 125) / 15); + loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 80) / 15); // Objects that are arranged in a mostly-linear fashion should be easy to read (such as circles in a stream). //if (loopObj.Angle.IsNotNull() && prevLoopObj.Angle.IsNotNull()) @@ -68,7 +68,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd var timeDifficultyFactor = 1200 / pastObjectDifficultyInfluence; hiddenDifficulty += 12 * timeSpentInvisible / timeDifficultyFactor; - hiddenDifficulty += 7 * currVelocity; + hiddenDifficulty += 2 * currVelocity; } double preemptDifficulty = 0.0; From f1f1e100a81e4474ea8940a5a2ccd44895823dbb Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Fri, 26 Aug 2022 00:05:38 +0100 Subject: [PATCH 11/96] Change low spacing influence, add time separation factor, and moar hidden stuff --- .../Evaluators/CognitionEvaluator.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 6ad7a336673a..13c2e6f79d7b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using FFmpeg.AutoGen; using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; @@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { public static class CognitionEvaluator { - private const double cognition_window_size = 2000; + private const double cognition_window_size = 3000; private const double note_density_difficulty_multiplier = 1.0; @@ -32,7 +31,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd var clockRateEstimate = current.BaseObject.StartTime / currObj.StartTime; List pastVisibleObjects = retrievePastVisibleObjects(currObj); - List currentVisibleObjects = retrieveCurrentVisibleObjects(currObj); + //List currentVisibleObjects = retrieveCurrentVisibleObjects(currObj); // Rather than note density being the number of on-screen objects visible at the current object, // consider it as how many objects the current object has been visible for. @@ -42,17 +41,18 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd foreach (var loopObj in pastVisibleObjects) { - var prevLoopObj = (OsuDifficultyHitObject)loopObj.Previous(0); - double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 80) / 15); + loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 90) / 15); // Objects that are arranged in a mostly-linear fashion should be easy to read (such as circles in a stream). //if (loopObj.Angle.IsNotNull() && prevLoopObj.Angle.IsNotNull()) // loopDifficulty *= 1 - Math.Pow(Math.Sin(0.5 * loopObj.Angle.Value), 5); + double timeBetweenCurrAndLoopObj = (currObj.BaseObject.StartTime - loopObj.BaseObject.StartTime) / clockRateEstimate; + loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); + pastObjectDifficultyInfluence += loopDifficulty; } @@ -67,7 +67,11 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd var timeDifficultyFactor = 1200 / pastObjectDifficultyInfluence; - hiddenDifficulty += 12 * timeSpentInvisible / timeDifficultyFactor; + hiddenDifficulty += Math.Pow(2 * timeSpentInvisible / timeDifficultyFactor, 1.6); + + if (isRhythmChange) + hiddenDifficulty *= 1.1; + hiddenDifficulty += 2 * currVelocity; } @@ -131,6 +135,11 @@ private static double getDurationSpentInvisible(OsuDifficultyHitObject current) return (fadeOutStartTime + fadeOutDuration) - (baseObject.StartTime - baseObject.TimePreempt); } + private static double getTimeNerfFactor(double deltaTime) + { + return Math.Clamp(2 - (deltaTime / 1500), 0, 1); + } + private static double logistic(double x) => 1 / (1 + Math.Pow(Math.E, -x)); } } From 35c72012e94df1691aa7ca295b5bf67b5d54e4f7 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Sat, 27 Aug 2022 18:33:07 +0100 Subject: [PATCH 12/96] Various high AR tweaks --- .../Evaluators/CognitionEvaluator.cs | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 13c2e6f79d7b..98905391b0d3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -46,10 +46,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 90) / 15); - // Objects that are arranged in a mostly-linear fashion should be easy to read (such as circles in a stream). - //if (loopObj.Angle.IsNotNull() && prevLoopObj.Angle.IsNotNull()) - // loopDifficulty *= 1 - Math.Pow(Math.Sin(0.5 * loopObj.Angle.Value), 5); - double timeBetweenCurrAndLoopObj = (currObj.BaseObject.StartTime - loopObj.BaseObject.StartTime) / clockRateEstimate; loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); @@ -76,14 +72,45 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd } double preemptDifficulty = 0.0; + if (currObj.preempt < 400) + { preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / 14; - // Buff rhythm on high AR. - preemptDifficulty *= RhythmEvaluator.EvaluateDifficultyOf(current, 30); + // Buff spacing. + preemptDifficulty *= Math.Max(1, 0.3 * currVelocity); + + // Buff rhythm. + preemptDifficulty *= RhythmEvaluator.EvaluateDifficultyOf(current, 30); + + // Buff small circles. + // Very arbitrary, but lets assume CS5 is when AR11 becomes more uncomfortable. + // This is likely going to need adjustments in the future as player meta develops. + preemptDifficulty *= 1 + Math.Max((30 - ((OsuHitObject)currObj.BaseObject).Radius) / 20, 0); + + // Nerf repeated angles on high AR. + if (current.Index > 1) + { + var prevPrevObj = (OsuDifficultyHitObject)current.Previous(1); + + if (currObj.Angle != null && prevObj.Angle != null) + { + preemptDifficulty *= getAngleDifferenceNerfFactor(Math.Abs(currObj.Angle.Value - prevObj.Angle.Value)); + } + + if (currObj.Angle != null && prevPrevObj.Angle != null) + { + preemptDifficulty *= getAngleDifferenceNerfFactor(Math.Abs(currObj.Angle.Value - prevPrevObj.Angle.Value)); + } + } + } double difficulty = Math.Max(preemptDifficulty, hiddenDifficulty) + noteDensityDifficulty; + // While there is slider leniency... + if (currObj.BaseObject is Slider) + difficulty *= 0.2; + return difficulty; } @@ -140,6 +167,8 @@ private static double getTimeNerfFactor(double deltaTime) return Math.Clamp(2 - (deltaTime / 1500), 0, 1); } + private static double getAngleDifferenceNerfFactor(double angleDifference) => 1 - 1 * Math.Cos(2 * Math.Min(Math.PI / 4, angleDifference)); + private static double logistic(double x) => 1 / (1 + Math.Pow(Math.E, -x)); } } From b0d47e4c35a3508b5ea6b7ea2f1730eb0cd4c4e3 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Sat, 27 Aug 2022 19:56:40 +0100 Subject: [PATCH 13/96] Allow hidden difficulty and high AR difficulty to co-exist. --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 98905391b0d3..56ebfe58c8fd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -105,7 +105,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd } } - double difficulty = Math.Max(preemptDifficulty, hiddenDifficulty) + noteDensityDifficulty; + double difficulty = preemptDifficulty + hiddenDifficulty + noteDensityDifficulty; // While there is slider leniency... if (currObj.BaseObject is Slider) From 97f3f28d9183e6a9c0151b429b264d6ce6660f31 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Sun, 28 Aug 2022 21:11:33 +0100 Subject: [PATCH 14/96] Time factor, improve spacing and rhythm buffs --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 56ebfe58c8fd..377e106928db 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -75,13 +75,13 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd if (currObj.preempt < 400) { - preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / 14; + preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / (10 + (currObj.StrainTime * 0.05)); // Buff spacing. - preemptDifficulty *= Math.Max(1, 0.3 * currVelocity); + preemptDifficulty *= 1 + 0.2 * currVelocity; // Buff rhythm. - preemptDifficulty *= RhythmEvaluator.EvaluateDifficultyOf(current, 30); + preemptDifficulty *= Math.Max(1, RhythmEvaluator.EvaluateDifficultyOf(current, 30) - 0.1); // Buff small circles. // Very arbitrary, but lets assume CS5 is when AR11 becomes more uncomfortable. From f1e14b3bb97dc75bb21d9665fe15373e89f5dcc6 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 31 Aug 2022 19:24:26 +0100 Subject: [PATCH 15/96] Add constant rhythm nerf, reduce repeat angle nerf, buff spacing --- .../Evaluators/CognitionEvaluator.cs | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 377e106928db..47d2c8823552 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -15,8 +15,6 @@ public static class CognitionEvaluator { private const double cognition_window_size = 3000; - private const double note_density_difficulty_multiplier = 1.0; - public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) { if (current.BaseObject is Spinner || current.Index == 0) @@ -52,7 +50,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd pastObjectDifficultyInfluence += loopDifficulty; } - noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 1)), 2.3) * note_density_difficulty_multiplier; + noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 1)), 2.3); double hiddenDifficulty = 0; @@ -78,7 +76,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / (10 + (currObj.StrainTime * 0.05)); // Buff spacing. - preemptDifficulty *= 1 + 0.2 * currVelocity; + preemptDifficulty *= 1 + 0.4 * currVelocity; // Buff rhythm. preemptDifficulty *= Math.Max(1, RhythmEvaluator.EvaluateDifficultyOf(current, 30) - 0.1); @@ -88,7 +86,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // This is likely going to need adjustments in the future as player meta develops. preemptDifficulty *= 1 + Math.Max((30 - ((OsuHitObject)currObj.BaseObject).Radius) / 20, 0); - // Nerf repeated angles on high AR. + // Nerf repeated angles. if (current.Index > 1) { var prevPrevObj = (OsuDifficultyHitObject)current.Previous(1); @@ -103,9 +101,12 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd preemptDifficulty *= getAngleDifferenceNerfFactor(Math.Abs(currObj.Angle.Value - prevPrevObj.Angle.Value)); } } + + // Nerf constant rhythm. + preemptDifficulty *= getConstantRhythmNerfFactor(currObj); } - double difficulty = preemptDifficulty + hiddenDifficulty + noteDensityDifficulty; + double difficulty = Math.Max(preemptDifficulty, hiddenDifficulty) + noteDensityDifficulty; // While there is slider leniency... if (currObj.BaseObject is Slider) @@ -162,12 +163,52 @@ private static double getDurationSpentInvisible(OsuDifficultyHitObject current) return (fadeOutStartTime + fadeOutDuration) - (baseObject.StartTime - baseObject.TimePreempt); } + private static double getConstantRhythmNerfFactor(OsuDifficultyHitObject current) + { + // Studies [citation needed] suggest that 33bpm is where humans stop interpreting notes as a part of a beat, + // instead interpreting them as individual events. We're gonna use this to both lessen the nerf of this factor, + // as well as using it as a convenient limit for how back in time we're gonna look for the calculation. + const double time_limit = 1800; + const double time_limit_low = 500; + + double constantRhythmCount = 0; + + int index = 0; + double currentTimeGap = 0; + + while (currentTimeGap < time_limit) + { + var loopObj = (OsuDifficultyHitObject)current.Previous(index); + + if (loopObj.IsNull()) + break; + + double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - time_limit_low) / (time_limit - time_limit_low), 0, 1); + + if (Math.Abs(current.StrainTime - loopObj.StrainTime) < 10) // constant rhythm, o-o-o-o + constantRhythmCount += 1.0 * longIntervalFactor; + else if (Math.Abs(current.StrainTime - loopObj.StrainTime * 2) < 10) // speed up rhythm, o---o-o + constantRhythmCount += 0.33 * longIntervalFactor; + else if (Math.Abs(current.StrainTime * 2 - loopObj.StrainTime) < 10) // slow down rhythm, o-o---o + constantRhythmCount += 0.33 * longIntervalFactor; + else + break; + + currentTimeGap = current.StartTime - loopObj.StartTime; + index++; + } + + double difficulty = Math.Pow(Math.Min(1, 2 / constantRhythmCount), 2); + + return difficulty; + } + private static double getTimeNerfFactor(double deltaTime) { return Math.Clamp(2 - (deltaTime / 1500), 0, 1); } - private static double getAngleDifferenceNerfFactor(double angleDifference) => 1 - 1 * Math.Cos(2 * Math.Min(Math.PI / 4, angleDifference)); + private static double getAngleDifferenceNerfFactor(double angleDifference) => 1 - 0.5 * Math.Cos(1 * Math.Min(Math.PI / 2, angleDifference)); private static double logistic(double x) => 1 / (1 + Math.Pow(Math.E, -x)); } From 979749371f7323443b825cb1a85832ebe776f7ff Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 13 Sep 2022 22:33:51 +0100 Subject: [PATCH 16/96] Nerf linear, readjust hidden parameters --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 47d2c8823552..92a6e092c4d6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -39,6 +39,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd foreach (var loopObj in pastVisibleObjects) { + var prevLoopObj = loopObj.Previous(0) as OsuDifficultyHitObject; + double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. @@ -47,6 +49,10 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd double timeBetweenCurrAndLoopObj = (currObj.BaseObject.StartTime - loopObj.BaseObject.StartTime) / clockRateEstimate; loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); + // Objects that are arranged in a mostly-linear fashion should be easy to read (such as circles in a stream). + if (prevLoopObj != null && loopObj.Angle.IsNotNull() && prevLoopObj.Angle.IsNotNull()) + loopDifficulty *= 1 - Math.Pow(Math.Sin(0.5 * loopObj.Angle.Value), 5); + pastObjectDifficultyInfluence += loopDifficulty; } @@ -59,9 +65,9 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd var timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; var isRhythmChange = (currObj.StrainTime - prevObj.StrainTime < 5); - var timeDifficultyFactor = 1200 / pastObjectDifficultyInfluence; + var timeDifficultyFactor = 800 / pastObjectDifficultyInfluence; - hiddenDifficulty += Math.Pow(2 * timeSpentInvisible / timeDifficultyFactor, 1.6); + hiddenDifficulty += Math.Pow(7 * timeSpentInvisible / timeDifficultyFactor, 1); if (isRhythmChange) hiddenDifficulty *= 1.1; From 8e614e260a65d0a3fefe7afd69f83140648933bf Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 5 Oct 2022 01:32:05 +0100 Subject: [PATCH 17/96] Don't nerf object influence if stream --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 92a6e092c4d6..d1502c279af9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -49,15 +49,19 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd double timeBetweenCurrAndLoopObj = (currObj.BaseObject.StartTime - loopObj.BaseObject.StartTime) / clockRateEstimate; loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); - // Objects that are arranged in a mostly-linear fashion should be easy to read (such as circles in a stream). - if (prevLoopObj != null && loopObj.Angle.IsNotNull() && prevLoopObj.Angle.IsNotNull()) - loopDifficulty *= 1 - Math.Pow(Math.Sin(0.5 * loopObj.Angle.Value), 5); - pastObjectDifficultyInfluence += loopDifficulty; } noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 1)), 2.3); + // Objects that are arranged in a mostly-linear fashion should be easy to read (such as circles in a stream). + if (currObj.Angle.IsNotNull() && prevObj.IsNotNull()) + { + double prevVelocity = prevObj.LazyJumpDistance / prevObj.StrainTime; + double velocityDifference = Math.Clamp(Math.Abs(currVelocity - prevVelocity), 0, 1); + noteDensityDifficulty *= 1 - velocityDifference * Math.Pow(Math.Sin(0.5 * currObj.Angle.Value), 5); + } + double hiddenDifficulty = 0; if (hidden) From cd0b2b2e3f3dc21f6c0d8df1916e3205908ff1b7 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Fri, 7 Oct 2022 20:26:56 +0100 Subject: [PATCH 18/96] Change some AR11 calc --- .../Evaluators/CognitionEvaluator.cs | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index d1502c279af9..217b368ef215 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -86,7 +86,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / (10 + (currObj.StrainTime * 0.05)); // Buff spacing. - preemptDifficulty *= 1 + 0.4 * currVelocity; + preemptDifficulty *= 1 + 0.6 * currVelocity; // Buff rhythm. preemptDifficulty *= Math.Max(1, RhythmEvaluator.EvaluateDifficultyOf(current, 30) - 0.1); @@ -99,17 +99,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Nerf repeated angles. if (current.Index > 1) { - var prevPrevObj = (OsuDifficultyHitObject)current.Previous(1); - - if (currObj.Angle != null && prevObj.Angle != null) - { - preemptDifficulty *= getAngleDifferenceNerfFactor(Math.Abs(currObj.Angle.Value - prevObj.Angle.Value)); - } - - if (currObj.Angle != null && prevPrevObj.Angle != null) - { - preemptDifficulty *= getAngleDifferenceNerfFactor(Math.Abs(currObj.Angle.Value - prevPrevObj.Angle.Value)); - } + preemptDifficulty *= getConstantAngleNerfFactor(currObj); + preemptDifficulty *= getConstantAngleNerfFactor(prevObj); } // Nerf constant rhythm. @@ -213,13 +204,46 @@ private static double getConstantRhythmNerfFactor(OsuDifficultyHitObject current return difficulty; } + private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) + { + const double time_limit = 2000; + const double time_limit_low = 300; + + double constantAngleCount = 0; + + int index = 0; + double currentTimeGap = 0; + + while (currentTimeGap < time_limit) + { + var loopObj = (OsuDifficultyHitObject)current.Previous(index); + + if (loopObj.IsNull()) + break; + + double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - time_limit_low) / (time_limit - time_limit_low), 0, 1); + + if (loopObj.Angle.IsNotNull() && current.Angle.IsNotNull()) + { + double angleDifference = Math.Abs(current.Angle.Value - loopObj.Angle.Value); + + constantAngleCount += Math.Cos(2 * Math.Min(Math.PI / 4, angleDifference)) * longIntervalFactor; + } + + currentTimeGap = current.StartTime - loopObj.StartTime; + index++; + } + + double difficulty = Math.Pow(Math.Min(1, 2 / constantAngleCount), 2); + + return difficulty; + } + private static double getTimeNerfFactor(double deltaTime) { return Math.Clamp(2 - (deltaTime / 1500), 0, 1); } - private static double getAngleDifferenceNerfFactor(double angleDifference) => 1 - 0.5 * Math.Cos(1 * Math.Min(Math.PI / 2, angleDifference)); - private static double logistic(double x) => 1 / (1 + Math.Pow(Math.E, -x)); } } From f42004f18df64f185cbb7d7a941d1098e89ddc0c Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Fri, 7 Oct 2022 20:40:38 +0100 Subject: [PATCH 19/96] Remove outdated argument --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 217b368ef215..413c7a6e0445 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -89,7 +89,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd preemptDifficulty *= 1 + 0.6 * currVelocity; // Buff rhythm. - preemptDifficulty *= Math.Max(1, RhythmEvaluator.EvaluateDifficultyOf(current, 30) - 0.1); + preemptDifficulty *= Math.Max(1, RhythmEvaluator.EvaluateDifficultyOf(current) - 0.1); // Buff small circles. // Very arbitrary, but lets assume CS5 is when AR11 becomes more uncomfortable. From b6d4fbf8b5977af6b362c2242f9338c79dccac4e Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 10 Oct 2022 13:49:38 +0100 Subject: [PATCH 20/96] Add constant angle nerf to note density difficulty --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 413c7a6e0445..be130d8ec744 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -53,14 +53,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd } noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 1)), 2.3); - - // Objects that are arranged in a mostly-linear fashion should be easy to read (such as circles in a stream). - if (currObj.Angle.IsNotNull() && prevObj.IsNotNull()) - { - double prevVelocity = prevObj.LazyJumpDistance / prevObj.StrainTime; - double velocityDifference = Math.Clamp(Math.Abs(currVelocity - prevVelocity), 0, 1); - noteDensityDifficulty *= 1 - velocityDifference * Math.Pow(Math.Sin(0.5 * currObj.Angle.Value), 5); - } + noteDensityDifficulty *= getConstantAngleNerfFactor(currObj); double hiddenDifficulty = 0; From d50e67f80ddb669d1c526e76f682f64fc3e2051e Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 17 Oct 2022 22:05:04 +0100 Subject: [PATCH 21/96] Remove WIP ar11 stuff from Cognition --- .../Evaluators/CognitionEvaluator.cs | 90 +------------------ .../Difficulty/OsuPerformanceCalculator.cs | 17 +++- 2 files changed, 17 insertions(+), 90 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index be130d8ec744..4074ba425ac4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -29,7 +29,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd var clockRateEstimate = current.BaseObject.StartTime / currObj.StartTime; List pastVisibleObjects = retrievePastVisibleObjects(currObj); - //List currentVisibleObjects = retrieveCurrentVisibleObjects(currObj); // Rather than note density being the number of on-screen objects visible at the current object, // consider it as how many objects the current object has been visible for. @@ -72,35 +71,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd hiddenDifficulty += 2 * currVelocity; } - double preemptDifficulty = 0.0; - - if (currObj.preempt < 400) - { - preemptDifficulty += Math.Pow(400 - currObj.preempt, 1.5) / (10 + (currObj.StrainTime * 0.05)); - - // Buff spacing. - preemptDifficulty *= 1 + 0.6 * currVelocity; - - // Buff rhythm. - preemptDifficulty *= Math.Max(1, RhythmEvaluator.EvaluateDifficultyOf(current) - 0.1); - - // Buff small circles. - // Very arbitrary, but lets assume CS5 is when AR11 becomes more uncomfortable. - // This is likely going to need adjustments in the future as player meta develops. - preemptDifficulty *= 1 + Math.Max((30 - ((OsuHitObject)currObj.BaseObject).Radius) / 20, 0); - - // Nerf repeated angles. - if (current.Index > 1) - { - preemptDifficulty *= getConstantAngleNerfFactor(currObj); - preemptDifficulty *= getConstantAngleNerfFactor(prevObj); - } - - // Nerf constant rhythm. - preemptDifficulty *= getConstantRhythmNerfFactor(currObj); - } - - double difficulty = Math.Max(preemptDifficulty, hiddenDifficulty) + noteDensityDifficulty; + double difficulty = hiddenDifficulty + noteDensityDifficulty; // While there is slider leniency... if (currObj.BaseObject is Slider) @@ -128,25 +99,6 @@ private static List retrievePastVisibleObjects(OsuDiffic return objects; } - // Returns a list of objects that are visible on screen at - // the point in time at which the current object needs is clicked. - private static List retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) - { - List objects = new List(); - - for (int i = 0; i < current.Count; i++) - { - OsuDifficultyHitObject loopObj = (OsuDifficultyHitObject)current.Next(i); - - if (loopObj.IsNull() || (loopObj.StartTime - current.StartTime) > cognition_window_size) - break; - - objects.Add(loopObj); - } - - return objects; - } - private static double getDurationSpentInvisible(OsuDifficultyHitObject current) { var baseObject = (OsuHitObject)current.BaseObject; @@ -157,46 +109,6 @@ private static double getDurationSpentInvisible(OsuDifficultyHitObject current) return (fadeOutStartTime + fadeOutDuration) - (baseObject.StartTime - baseObject.TimePreempt); } - private static double getConstantRhythmNerfFactor(OsuDifficultyHitObject current) - { - // Studies [citation needed] suggest that 33bpm is where humans stop interpreting notes as a part of a beat, - // instead interpreting them as individual events. We're gonna use this to both lessen the nerf of this factor, - // as well as using it as a convenient limit for how back in time we're gonna look for the calculation. - const double time_limit = 1800; - const double time_limit_low = 500; - - double constantRhythmCount = 0; - - int index = 0; - double currentTimeGap = 0; - - while (currentTimeGap < time_limit) - { - var loopObj = (OsuDifficultyHitObject)current.Previous(index); - - if (loopObj.IsNull()) - break; - - double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - time_limit_low) / (time_limit - time_limit_low), 0, 1); - - if (Math.Abs(current.StrainTime - loopObj.StrainTime) < 10) // constant rhythm, o-o-o-o - constantRhythmCount += 1.0 * longIntervalFactor; - else if (Math.Abs(current.StrainTime - loopObj.StrainTime * 2) < 10) // speed up rhythm, o---o-o - constantRhythmCount += 0.33 * longIntervalFactor; - else if (Math.Abs(current.StrainTime * 2 - loopObj.StrainTime) < 10) // slow down rhythm, o-o---o - constantRhythmCount += 0.33 * longIntervalFactor; - else - break; - - currentTimeGap = current.StartTime - loopObj.StartTime; - index++; - } - - double difficulty = Math.Pow(Math.Min(1, 2 / constantRhythmCount), 2); - - return difficulty; - } - private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) { const double time_limit = 2000; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 80b3796bc785..dc337da81361 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -102,7 +102,16 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut aimValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount); aimValue *= getComboScalingFactor(attributes); - + + double approachRateFactor = 0.0; + if (attributes.ApproachRate > 10.33) + approachRateFactor = 0.4 * (attributes.ApproachRate - 10.33); + + if (score.Mods.Any(h => h is OsuModRelax)) + approachRateFactor = 0.0; + + aimValue *= 1.0 + approachRateFactor; + if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); @@ -140,6 +149,12 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib speedValue *= getComboScalingFactor(attributes); + double approachRateFactor = 0.0; + if (attributes.ApproachRate > 10.33) + approachRateFactor = 0.4 * (attributes.ApproachRate - 10.33); + + speedValue *= 1.0 + approachRateFactor; + if (score.Mods.Any(m => m is OsuModBlinds)) { // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. From 441bea57010863534bbcf6e659b01ccf5c9ff8f3 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 17 Oct 2022 22:15:31 +0100 Subject: [PATCH 22/96] Make angle nerf requirement narrower --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 4074ba425ac4..7b1c24013620 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -112,7 +112,7 @@ private static double getDurationSpentInvisible(OsuDifficultyHitObject current) private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) { const double time_limit = 2000; - const double time_limit_low = 300; + const double time_limit_low = 500; double constantAngleCount = 0; @@ -132,7 +132,7 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) { double angleDifference = Math.Abs(current.Angle.Value - loopObj.Angle.Value); - constantAngleCount += Math.Cos(2 * Math.Min(Math.PI / 4, angleDifference)) * longIntervalFactor; + constantAngleCount += Math.Cos(4 * Math.Min(Math.PI / 8, angleDifference)) * longIntervalFactor; } currentTimeGap = current.StartTime - loopObj.StartTime; From 090b408229e5776907b52b6fe7d044154d10b801 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:09:49 +0100 Subject: [PATCH 23/96] Apply various changes --- .../Difficulty/Evaluators/CognitionEvaluator.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs index 7b1c24013620..767ae544203b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs @@ -43,7 +43,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 90) / 15); + loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 80) / 15); double timeBetweenCurrAndLoopObj = (currObj.BaseObject.StartTime - loopObj.BaseObject.StartTime) / clockRateEstimate; loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); @@ -52,30 +52,20 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd } noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 1)), 2.3); - noteDensityDifficulty *= getConstantAngleNerfFactor(currObj); double hiddenDifficulty = 0; if (hidden) { var timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; - var isRhythmChange = (currObj.StrainTime - prevObj.StrainTime < 5); - var timeDifficultyFactor = 800 / pastObjectDifficultyInfluence; hiddenDifficulty += Math.Pow(7 * timeSpentInvisible / timeDifficultyFactor, 1); - - if (isRhythmChange) - hiddenDifficulty *= 1.1; - hiddenDifficulty += 2 * currVelocity; } double difficulty = hiddenDifficulty + noteDensityDifficulty; - - // While there is slider leniency... - if (currObj.BaseObject is Slider) - difficulty *= 0.2; + difficulty *= getConstantAngleNerfFactor(currObj); return difficulty; } @@ -112,7 +102,7 @@ private static double getDurationSpentInvisible(OsuDifficultyHitObject current) private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) { const double time_limit = 2000; - const double time_limit_low = 500; + const double time_limit_low = 200; double constantAngleCount = 0; From 82cbdccb57a57867da7b117f159e0ca50162891b Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:13:25 +0100 Subject: [PATCH 24/96] Rename cognition to reading --- ...nitionEvaluator.cs => ReadingEvaluator.cs} | 10 +++---- .../Difficulty/OsuDifficultyAttributes.cs | 6 ++--- .../Difficulty/OsuDifficultyCalculator.cs | 10 +++---- .../Difficulty/OsuPerformanceAttributes.cs | 6 ++--- .../Difficulty/OsuPerformanceCalculator.cs | 26 +++++++++---------- .../Skills/{Cognition.cs => Reading.cs} | 6 ++--- 6 files changed, 30 insertions(+), 34 deletions(-) rename osu.Game.Rulesets.Osu/Difficulty/Evaluators/{CognitionEvaluator.cs => ReadingEvaluator.cs} (94%) rename osu.Game.Rulesets.Osu/Difficulty/Skills/{Cognition.cs => Reading.cs} (89%) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs similarity index 94% rename from osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs rename to osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 767ae544203b..da95954e9d47 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/CognitionEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -11,9 +11,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { - public static class CognitionEvaluator + public static class ReadingEvaluator { - private const double cognition_window_size = 3000; + private const double reading_window_size = 3000; public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) { @@ -21,8 +21,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd return 0; var currObj = (OsuDifficultyHitObject)current; - var prevObj = (OsuDifficultyHitObject)current.Previous(0); - double currVelocity = currObj.LazyJumpDistance / currObj.StrainTime; // Maybe I should just pass in clockrate... @@ -38,8 +36,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd foreach (var loopObj in pastVisibleObjects) { - var prevLoopObj = loopObj.Previous(0) as OsuDifficultyHitObject; - double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. @@ -80,7 +76,7 @@ private static List retrievePastVisibleObjects(OsuDiffic { OsuDifficultyHitObject loopObj = (OsuDifficultyHitObject)current.Previous(i); - if (loopObj.IsNull() || current.StartTime - loopObj.StartTime > cognition_window_size) + if (loopObj.IsNull() || current.StartTime - loopObj.StartTime > reading_window_size) break; objects.Add(loopObj); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index e3a14dee0754..2d8eaea91ff5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -39,10 +39,10 @@ public class OsuDifficultyAttributes : DifficultyAttributes public double FlashlightDifficulty { get; set; } /// - /// The difficulty corresponding to the cognition skill. + /// The difficulty corresponding to the reading skill. /// - [JsonProperty("cognition_difficulty")] - public double CognitionDifficulty { get; set; } + [JsonProperty("reading_difficulty")] + public double ReadingDifficulty { get; set; } /// /// Describes how much of is contributed to by hitcircles or sliders. diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index a0bc4e6c2ca8..df954dbf6b5b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -41,7 +41,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; double speedNotes = ((Speed)skills[2]).RelevantNoteCount(); double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; - double cognitionRating = Math.Sqrt(skills[4].DifficultyValue()) * difficulty_multiplier; + double readingRating = Math.Sqrt(skills[4].DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; @@ -65,14 +65,14 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat if (mods.Any(h => h is OsuModFlashlight)) baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; - double baseCognitionPerformance = Math.Pow(5 * Math.Max(1, cognitionRating / 0.0675) - 4, 3) / 100000; + double baseReadingPerformance = Math.Pow(5 * Math.Max(1, readingRating / 0.0675) - 4, 3) / 100000; double basePerformance = Math.Pow( Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1) + Math.Pow(baseFlashlightPerformance, 1.1) + - Math.Pow(baseCognitionPerformance, 1.1), 1.0 / 1.1 + Math.Pow(baseReadingPerformance, 1.1), 1.0 / 1.1 ); double starRating = basePerformance > 0.00001 ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; @@ -98,7 +98,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat SpeedDifficulty = speedRating, SpeedNoteCount = speedNotes, FlashlightDifficulty = flashlightRating, - CognitionDifficulty = cognitionRating, + ReadingDifficulty = readingRating, SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, @@ -133,7 +133,7 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo new Aim(mods, false), new Speed(mods), new Flashlight(mods), - new Cognition(mods), + new Reading(mods), }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 12ac7ef607ad..1d880e9842bb 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -23,8 +23,8 @@ public class OsuPerformanceAttributes : PerformanceAttributes [JsonProperty("flashlight")] public double Flashlight { get; set; } - [JsonProperty("cognition")] - public double Cognition { get; set; } + [JsonProperty("reading")] + public double Reading { get; set; } [JsonProperty("effective_miss_count")] public double EffectiveMissCount { get; set; } @@ -38,7 +38,7 @@ public override IEnumerable GetAttributesForDisplay yield return new PerformanceDisplayAttribute(nameof(Speed), "Speed", Speed); yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); yield return new PerformanceDisplayAttribute(nameof(Flashlight), "Flashlight Bonus", Flashlight); - yield return new PerformanceDisplayAttribute(nameof(Cognition), "Cognition", Cognition); + yield return new PerformanceDisplayAttribute(nameof(Reading), "Reading", Reading); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index dc337da81361..c18f13961bf0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -67,14 +67,14 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double speedValue = computeSpeedValue(score, osuAttributes); double accuracyValue = computeAccuracyValue(score, osuAttributes); double flashlightValue = computeFlashlightValue(score, osuAttributes); - double cognitionValue = computeCognitionValue(score, osuAttributes); + double readingValue = computeReadingValue(score, osuAttributes); double totalValue = Math.Pow( Math.Pow(aimValue, 1.1) + Math.Pow(speedValue, 1.1) + Math.Pow(accuracyValue, 1.1) + Math.Pow(flashlightValue, 1.1) + - Math.Pow(cognitionValue, 1.1), 1.0 / 1.1 + Math.Pow(readingValue, 1.1), 1.0 / 1.1 ) * multiplier; return new OsuPerformanceAttributes @@ -83,7 +83,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s Speed = speedValue, Accuracy = accuracyValue, Flashlight = flashlightValue, - Cognition = cognitionValue, + Reading = readingValue, EffectiveMissCount = effectiveMissCount, Total = totalValue }; @@ -237,27 +237,27 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a return flashlightValue; } - private double computeCognitionValue(ScoreInfo score, OsuDifficultyAttributes attributes) + private double computeReadingValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double rawCognition = attributes.CognitionDifficulty; + double rawReading = attributes.ReadingDifficulty; if (score.Mods.Any(m => m is OsuModTouchDevice)) - rawCognition = Math.Pow(rawCognition, 0.8); + rawReading = Math.Pow(rawReading, 0.8); - double cognitionValue = Math.Pow(rawCognition, 2.0) * 25.0; + double readingValue = Math.Pow(rawReading, 2.0) * 25.0; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) - cognitionValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); + readingValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); - cognitionValue *= getComboScalingFactor(attributes); + readingValue *= getComboScalingFactor(attributes); - // Scale the cognition value with accuracy _harshly_. - cognitionValue *= accuracy * accuracy; + // Scale the reading value with accuracy _harshly_. + readingValue *= accuracy * accuracy; // It is important to also consider accuracy difficulty when doing that. - cognitionValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; + readingValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; - return cognitionValue; + return readingValue; } private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs similarity index 89% rename from osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs rename to osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 2b59fbf1eef5..1126c14cdadc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Cognition.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -11,19 +11,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { - public class Cognition : Skill + public class Reading : Skill { private readonly List difficulties = new List(); private readonly bool hasHiddenMod; private const double skill_multiplier = 2.4; - public Cognition(Mod[] mods) + public Reading(Mod[] mods) : base(mods) { hasHiddenMod = mods.Any(m => m is OsuModHidden); } - public override void Process(DifficultyHitObject current) => difficulties.Add(CognitionEvaluator.EvaluateDifficultyOf(current, hasHiddenMod) * skill_multiplier); + public override void Process(DifficultyHitObject current) => difficulties.Add(ReadingEvaluator.EvaluateDifficultyOf(current, hasHiddenMod) * skill_multiplier); public override double DifficultyValue() { From a7070b5168549a565bf707016e4f00c92635717c Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 19 Oct 2022 12:56:21 +0100 Subject: [PATCH 25/96] Improve code quality --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index da95954e9d47..e7d8f8067c15 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -26,15 +26,9 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Maybe I should just pass in clockrate... var clockRateEstimate = current.BaseObject.StartTime / currObj.StartTime; - List pastVisibleObjects = retrievePastVisibleObjects(currObj); - - // Rather than note density being the number of on-screen objects visible at the current object, - // consider it as how many objects the current object has been visible for. - double noteDensityDifficulty = 1.0; - double pastObjectDifficultyInfluence = 1.0; - foreach (var loopObj in pastVisibleObjects) + foreach (var loopObj in retrievePastVisibleObjects(currObj)) { double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); @@ -47,17 +41,17 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd pastObjectDifficultyInfluence += loopDifficulty; } - noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 1)), 2.3); + double noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 1)), 2.3); double hiddenDifficulty = 0; if (hidden) { - var timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; - var timeDifficultyFactor = 800 / pastObjectDifficultyInfluence; + double timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; + double timeDifficultyFactor = 800 / pastObjectDifficultyInfluence; - hiddenDifficulty += Math.Pow(7 * timeSpentInvisible / timeDifficultyFactor, 1); - hiddenDifficulty += 2 * currVelocity; + hiddenDifficulty += Math.Pow(7 * timeSpentInvisible / timeDifficultyFactor, 1) + + 2 * currVelocity; } double difficulty = hiddenDifficulty + noteDensityDifficulty; @@ -68,21 +62,19 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Returns a list of objects that are visible on screen at // the point in time at which the current object becomes visible. - private static List retrievePastVisibleObjects(OsuDifficultyHitObject current) + private static IEnumerable retrievePastVisibleObjects(OsuDifficultyHitObject current) { - List objects = new List(); - for (int i = 0; i < current.Index; i++) { - OsuDifficultyHitObject loopObj = (OsuDifficultyHitObject)current.Previous(i); + OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i); - if (loopObj.IsNull() || current.StartTime - loopObj.StartTime > reading_window_size) + if (hitObject.IsNull() || + current.StartTime - hitObject.StartTime > reading_window_size || + hitObject.StartTime < current.StartTime - current.preempt) break; - objects.Add(loopObj); + yield return hitObject; } - - return objects; } private static double getDurationSpentInvisible(OsuDifficultyHitObject current) @@ -101,7 +93,6 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) const double time_limit_low = 200; double constantAngleCount = 0; - int index = 0; double currentTimeGap = 0; @@ -117,7 +108,6 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) if (loopObj.Angle.IsNotNull() && current.Angle.IsNotNull()) { double angleDifference = Math.Abs(current.Angle.Value - loopObj.Angle.Value); - constantAngleCount += Math.Cos(4 * Math.Min(Math.PI / 8, angleDifference)) * longIntervalFactor; } @@ -125,14 +115,12 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) index++; } - double difficulty = Math.Pow(Math.Min(1, 2 / constantAngleCount), 2); - - return difficulty; + return Math.Pow(Math.Min(1, 2 / constantAngleCount), 2); } private static double getTimeNerfFactor(double deltaTime) { - return Math.Clamp(2 - (deltaTime / 1500), 0, 1); + return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); } private static double logistic(double x) => 1 / (1 + Math.Pow(Math.E, -x)); From a9e9e50b8e685fbf229620966a0d241f6d574b6d Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 19 Oct 2022 13:00:00 +0100 Subject: [PATCH 26/96] Capitalize member --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 2 +- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index e7d8f8067c15..a5b4b0f4aa1c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -70,7 +70,7 @@ private static IEnumerable retrievePastVisibleObjects(Os if (hitObject.IsNull() || current.StartTime - hitObject.StartTime > reading_window_size || - hitObject.StartTime < current.StartTime - current.preempt) + hitObject.StartTime < current.StartTime - current.Preempt) break; yield return hitObject; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index cbcc4363909e..2fb9785af512 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -86,14 +86,14 @@ public class OsuDifficultyHitObject : DifficultyHitObject private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; - public readonly double preempt; + public readonly double Preempt; public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate, List objects, int index) : base(hitObject, lastObject, clockRate, objects, index) { this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; - preempt = BaseObject.TimePreempt / clockRate; + Preempt = BaseObject.TimePreempt / clockRate; // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. StrainTime = Math.Max(DeltaTime, min_delta_time); From 4a535fedb0f622a5d4e5507f99162c2094929a6e Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 24 Oct 2022 02:10:16 +0100 Subject: [PATCH 27/96] Revert high AR changes --- .../Difficulty/OsuPerformanceCalculator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index c18f13961bf0..4174d3c400e8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -105,12 +105,12 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) - approachRateFactor = 0.4 * (attributes.ApproachRate - 10.33); + approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); if (score.Mods.Any(h => h is OsuModRelax)) approachRateFactor = 0.0; - aimValue *= 1.0 + approachRateFactor; + aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); @@ -151,9 +151,9 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) - approachRateFactor = 0.4 * (attributes.ApproachRate - 10.33); + approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); - speedValue *= 1.0 + approachRateFactor; + speedValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. if (score.Mods.Any(m => m is OsuModBlinds)) { From c1712740f7981d9e5056812219028ad775483e1f Mon Sep 17 00:00:00 2001 From: js1086 Date: Sun, 30 Jul 2023 12:51:36 +0100 Subject: [PATCH 28/96] Balance streams and HD aim --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index a5b4b0f4aa1c..d419a476ab05 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -33,7 +33,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 80) / 15); + loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 90) / 15); double timeBetweenCurrAndLoopObj = (currObj.BaseObject.StartTime - loopObj.BaseObject.StartTime) / clockRateEstimate; loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); @@ -48,10 +48,12 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd if (hidden) { double timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; - double timeDifficultyFactor = 800 / pastObjectDifficultyInfluence; + double timeDifficultyFactor = 1000 / pastObjectDifficultyInfluence; - hiddenDifficulty += Math.Pow(7 * timeSpentInvisible / timeDifficultyFactor, 1) + - 2 * currVelocity; + double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 3, 1, 10); + + hiddenDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible / timeDifficultyFactor, 1) + + visibleObjectFactor * 4 * currVelocity; } double difficulty = hiddenDifficulty + noteDensityDifficulty; @@ -77,6 +79,25 @@ private static IEnumerable retrievePastVisibleObjects(Os } } + private static List retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) + { + List objects = new List(); + + for (int i = 0; i < current.Count; i++) + { + OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Next(i); + + if (hitObject.IsNull() || + (hitObject.StartTime - current.StartTime) > reading_window_size || + current.StartTime < hitObject.StartTime - hitObject.Preempt) + break; + + objects.Add(hitObject); + } + + return objects; + } + private static double getDurationSpentInvisible(OsuDifficultyHitObject current) { var baseObject = (OsuHitObject)current.BaseObject; From 07df5d9c6e6d05b6ad1fdc2294c0938304e41c5d Mon Sep 17 00:00:00 2001 From: js1086 Date: Thu, 10 Aug 2023 00:30:11 +0100 Subject: [PATCH 29/96] Further balance values --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index d419a476ab05..3e07ad418543 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -41,7 +41,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd pastObjectDifficultyInfluence += loopDifficulty; } - double noteDensityDifficulty = Math.Pow(3 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 1)), 2.3); + double noteDensityDifficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 3)), 2.3); double hiddenDifficulty = 0; @@ -50,10 +50,10 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd double timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; double timeDifficultyFactor = 1000 / pastObjectDifficultyInfluence; - double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 3, 1, 10); + double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15); hiddenDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible / timeDifficultyFactor, 1) + - visibleObjectFactor * 4 * currVelocity; + (8 + visibleObjectFactor) * currVelocity; } double difficulty = hiddenDifficulty + noteDensityDifficulty; From ffeb46af9b9d4c4d2b0013f107404c717a5f2258 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 8 Jan 2024 14:53:38 +0200 Subject: [PATCH 30/96] Initial overlap calc --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 40 ++++++++++- .../Difficulty/OsuPerformanceCalculator.cs | 18 +++++ .../Preprocessing/OsuDifficultyHitObject.cs | 70 +++++++++++++++++++ 3 files changed, 126 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 3e07ad418543..9e80cc1732be 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { @@ -15,6 +16,10 @@ public static class ReadingEvaluator { private const double reading_window_size = 3000; + private const double hidden_multiplier = 0.0; + private const double density_multiplier = 0.0; + private const double overlap_multiplier = 25.0; + public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) { if (current.BaseObject is Spinner || current.Index == 0) @@ -27,6 +32,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd var clockRateEstimate = current.BaseObject.StartTime / currObj.StartTime; double pastObjectDifficultyInfluence = 1.0; + double screenOverlapDifficulty = 0; foreach (var loopObj in retrievePastVisibleObjects(currObj)) { @@ -39,8 +45,23 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); pastObjectDifficultyInfluence += loopDifficulty; + + double lastOverlapness = 0; + foreach (var overlapObj in loopObj.OverlapObjects) + { + if (overlapObj.HitObject.StartTime + overlapObj.HitObject.Preempt > currObj.StartTime) break; + lastOverlapness = overlapObj.Overlapness; + } + screenOverlapDifficulty += lastOverlapness; } + if (screenOverlapDifficulty > 0) + { + Console.WriteLine($"Object {currObj.StartTime}, overlapness = {screenOverlapDifficulty:0.##}"); + } + + // Console.WriteLine($"Object {currObj.StartTime}, overlapness = {overlapDifficulty:0.##}"); + double noteDensityDifficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 3)), 2.3); double hiddenDifficulty = 0; @@ -56,7 +77,10 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd (8 + visibleObjectFactor) * currVelocity; } - double difficulty = hiddenDifficulty + noteDensityDifficulty; + double difficulty = hidden_multiplier * hiddenDifficulty + density_multiplier * noteDensityDifficulty + overlap_multiplier * screenOverlapDifficulty; + + // Console.WriteLine($"Object {currObj.StartTime}, {hiddenDifficulty:0.##} + {noteDensityDifficulty:0.##} + {overlapDifficulty:0.##}"); + difficulty *= getConstantAngleNerfFactor(currObj); return difficulty; @@ -139,11 +163,23 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) return Math.Pow(Math.Min(1, 2 / constantAngleCount), 2); } + private static double getOverlapness(OsuDifficultyHitObject odho1, OsuDifficultyHitObject odho2) + { + OsuHitObject o1 = (OsuHitObject)odho1.BaseObject, o2 = (OsuHitObject)odho2.BaseObject; + + double distance = Vector2.Distance(o1.StackedPosition, o2.StackedPosition); + + if (distance > o1.Radius * 2) + return 0; + if (distance < o1.Radius) + return 1; + return 1 - Math.Pow((distance - o1.Radius) / o1.Radius, 2); + } private static double getTimeNerfFactor(double deltaTime) { return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); } - private static double logistic(double x) => 1 / (1 + Math.Pow(Math.E, -x)); + private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 1fb38d47a8c8..ec38da793dd0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -104,6 +104,9 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); + // for testing + else if (attributes.ApproachRate < 8.0) + approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate); if (score.Mods.Any(h => h is OsuModRelax)) approachRateFactor = 0.0; @@ -112,6 +115,12 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); + // for testing + else if (score.Mods.Any(h => h is OsuModHidden)) + { + // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. + aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); + } // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = attributes.SliderCount * 0.15; @@ -158,6 +167,12 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. speedValue *= 1.12; } + // for testing + else if (score.Mods.Any(m => m is OsuModHidden)) + { + // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. + speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); + } // Calculate accuracy assuming the worst case scenario double relevantTotalDiff = totalHits - attributes.SpeedNoteCount; @@ -203,6 +218,9 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; + // For testing + else if (score.Mods.Any(m => m is OsuModHidden)) + accuracyValue *= 1.08; if (score.Mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 8b31e53025e9..ce9ba96faa12 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Channels; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Mods; @@ -83,6 +85,11 @@ public class OsuDifficultyHitObject : DifficultyHitObject /// public double HitWindowGreat { get; private set; } + /// + /// Objects that was visible after the note was hit together with cumulative overlapping difficulty. + /// + public IList OverlapObjects { get; private set; } + private readonly OsuHitObject? lastLastObject; private readonly OsuHitObject lastObject; public readonly double Preempt; @@ -107,6 +114,57 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje } setDistances(clockRate); + + OverlapObjects = new List(); + + double totalOverlapnessDifficulty = 0; + + OsuDifficultyHitObject prevObject = this; + + foreach (var loopObj in retrieveCurrentVisibleObjects(this)) + { + double currentOverlapness = calculateOverlapness(this, loopObj); // overlapness with this object + double instantOverlapness = 0.5 + calculateOverlapness(prevObject, loopObj); // overlapness between current and prev to make streams have 0 buff + + double angleFactor = 1; + if (loopObj.Angle != null) angleFactor += (-Math.Cos((double)loopObj.Angle) + 1) / 2; // =2 for wide angles, =1 for acute angles + instantOverlapness = Math.Min(1, instantOverlapness * angleFactor); // wide angles are more predictable + + totalOverlapnessDifficulty += currentOverlapness * (1 - instantOverlapness); // wide angles will have close-to-zero buff + + OverlapObjects.Add(new OverlapObject(loopObj, totalOverlapnessDifficulty)); + prevObject = loopObj; + } + } + + private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDifficultyHitObject odho2) + { + OsuHitObject o1 = odho1.BaseObject, o2 = odho2.BaseObject; + + double distance = Vector2.Distance(o1.StackedPosition, o2.StackedPosition); + + if (distance > o1.Radius * 2) + return 0; + if (distance < o1.Radius) + return 1; + return 1 - Math.Pow((distance - o1.Radius) / o1.Radius, 2); + } + + private static IEnumerable retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) + { + + for (int i = 0; i < current.Count; i++) + { + OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i); + + if (hitObject.IsNull() || + // (hitObject.StartTime - current.StartTime) > reading_window_size || + //current.StartTime < hitObject.StartTime - hitObject.Preempt) + hitObject.StartTime < current.StartTime - current.Preempt) + break; + + yield return hitObject; + } } public double OpacityAt(double time, bool hidden) @@ -323,5 +381,17 @@ private Vector2 getEndCursorPosition(OsuHitObject hitObject) return pos; } + + public struct OverlapObject + { + public OsuDifficultyHitObject HitObject; + public double Overlapness; + + public OverlapObject(OsuDifficultyHitObject hitObject, double overlapness) + { + HitObject = hitObject; + Overlapness = overlapness; + } + } } } From c64430f5483d590aa99be116100cbfe02c522edf Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 16 Jan 2024 02:03:11 +0200 Subject: [PATCH 31/96] Improved overlap calc and added GraphSkill to allow graph of reading difficulty --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 30 +++----- .../Difficulty/OsuDifficultyCalculator.cs | 11 +-- .../Difficulty/OsuPerformanceCalculator.cs | 4 +- .../Preprocessing/OsuDifficultyHitObject.cs | 69 +++++++++++++++++-- .../Difficulty/Skills/Reading.cs | 21 +++++- .../Rulesets/Difficulty/Skills/GraphSkill.cs | 39 +++++++++++ .../Rulesets/Difficulty/Skills/StrainSkill.cs | 34 +++------ 7 files changed, 148 insertions(+), 60 deletions(-) create mode 100644 osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 9e80cc1732be..8abc64a135bc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; -using osuTK; namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { @@ -17,8 +16,8 @@ public static class ReadingEvaluator private const double reading_window_size = 3000; private const double hidden_multiplier = 0.0; - private const double density_multiplier = 0.0; - private const double overlap_multiplier = 25.0; + private const double density_multiplier = 1.0; + private const double overlap_multiplier = 0.5; public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) { @@ -60,8 +59,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd Console.WriteLine($"Object {currObj.StartTime}, overlapness = {screenOverlapDifficulty:0.##}"); } - // Console.WriteLine($"Object {currObj.StartTime}, overlapness = {overlapDifficulty:0.##}"); - double noteDensityDifficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 3)), 2.3); double hiddenDifficulty = 0; @@ -77,12 +74,18 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd (8 + visibleObjectFactor) * currVelocity; } - double difficulty = hidden_multiplier * hiddenDifficulty + density_multiplier * noteDensityDifficulty + overlap_multiplier * screenOverlapDifficulty; + double difficulty = density_multiplier * noteDensityDifficulty; - // Console.WriteLine($"Object {currObj.StartTime}, {hiddenDifficulty:0.##} + {noteDensityDifficulty:0.##} + {overlapDifficulty:0.##}"); + screenOverlapDifficulty = Math.Max(0, screenOverlapDifficulty - 0.5); // make overlap value =1 cost significantly less + difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty; difficulty *= getConstantAngleNerfFactor(currObj); + + difficulty += hidden_multiplier * hiddenDifficulty; + + // Console.WriteLine($"Object {currObj.StartTime}, {hiddenDifficulty:0.##} + {noteDensityDifficulty:0.##} + {overlapDifficulty:0.##}"); + return difficulty; } @@ -162,19 +165,6 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) return Math.Pow(Math.Min(1, 2 / constantAngleCount), 2); } - - private static double getOverlapness(OsuDifficultyHitObject odho1, OsuDifficultyHitObject odho2) - { - OsuHitObject o1 = (OsuHitObject)odho1.BaseObject, o2 = (OsuHitObject)odho2.BaseObject; - - double distance = Vector2.Distance(o1.StackedPosition, o2.StackedPosition); - - if (distance > o1.Radius * 2) - return 0; - if (distance < o1.Radius) - return 1; - return 1 - Math.Pow((distance - o1.Radius) / o1.Radius, 2); - } private static double getTimeNerfFactor(double deltaTime) { return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index cd707e4bc03d..e956da37d2cf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -25,6 +25,7 @@ public class OsuDifficultyCalculator : DifficultyCalculator private const double difficulty_multiplier = 0.0675; public override int Version => 20220902; + public double SumPower => 1.1; public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -65,14 +66,14 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat if (mods.Any(h => h is OsuModFlashlight)) baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; - double baseReadingPerformance = Math.Pow(5 * Math.Max(1, readingRating / 0.0675) - 4, 3) / 100000; + double baseReadingPerformance = Math.Pow(readingRating, 2.0) * 25.0; double basePerformance = Math.Pow( - Math.Pow(baseAimPerformance, 1.1) + - Math.Pow(baseSpeedPerformance, 1.1) + - Math.Pow(baseFlashlightPerformance, 1.1) + - Math.Pow(baseReadingPerformance, 1.1), 1.0 / 1.1 + Math.Pow(baseAimPerformance, SumPower) + + Math.Pow(baseSpeedPerformance, SumPower) + + Math.Pow(baseFlashlightPerformance, SumPower) + + Math.Pow(baseReadingPerformance, SumPower), 1.0 / SumPower ); double starRating = basePerformance > 0.00001 diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index ec38da793dd0..9c27806221c9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -105,8 +105,8 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut if (attributes.ApproachRate > 10.33) approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); // for testing - else if (attributes.ApproachRate < 8.0) - approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate); + //else if (attributes.ApproachRate < 8.0) + // approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate); if (score.Mods.Any(h => h is OsuModRelax)) approachRateFactor = 0.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index ce9ba96faa12..58f613f961eb 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -119,8 +119,15 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje double totalOverlapnessDifficulty = 0; + double prevTimeWithoutOverlap = 0; + double timeWithoutOverlap = 0; + OsuDifficultyHitObject prevObject = this; + bool log = false; + + if (log) Console.WriteLine($"Checking for object {hitObject.StartTime}"); + foreach (var loopObj in retrieveCurrentVisibleObjects(this)) { double currentOverlapness = calculateOverlapness(this, loopObj); // overlapness with this object @@ -130,24 +137,76 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje if (loopObj.Angle != null) angleFactor += (-Math.Cos((double)loopObj.Angle) + 1) / 2; // =2 for wide angles, =1 for acute angles instantOverlapness = Math.Min(1, instantOverlapness * angleFactor); // wide angles are more predictable - totalOverlapnessDifficulty += currentOverlapness * (1 - instantOverlapness); // wide angles will have close-to-zero buff + if (log) Console.WriteLine($"Base overlapness - {currentOverlapness}"); + + currentOverlapness *= (1 - instantOverlapness) * 2; // wide angles will have close-to-zero buff + + if (log) Console.WriteLine($"Adjusted overlapness - {currentOverlapness}"); + + if (currentOverlapness > 0) + { + double difference = Math.Min(timeWithoutOverlap, prevTimeWithoutOverlap) / Math.Max(timeWithoutOverlap, prevTimeWithoutOverlap); + if (Math.Max(timeWithoutOverlap, prevTimeWithoutOverlap) == 0) difference = 0; + + currentOverlapness *= differenceActuation(difference); + + if (log) Console.WriteLine($"Overlapness [{prevTimeWithoutOverlap} -> {timeWithoutOverlap}], difference {difference}, actuation {differenceActuation(difference)}, result {currentOverlapness}"); + + prevTimeWithoutOverlap = timeWithoutOverlap; + timeWithoutOverlap = 0; + } + + else + { + timeWithoutOverlap += prevObject.DeltaTime; + + if (log) Console.WriteLine($"No overlapness, adding {prevObject.DeltaTime}, result {timeWithoutOverlap}"); + } + totalOverlapnessDifficulty += currentOverlapness; OverlapObjects.Add(new OverlapObject(loopObj, totalOverlapnessDifficulty)); prevObject = loopObj; + + if (log) Console.WriteLine($"Added object with difficulty {totalOverlapnessDifficulty}\n"); } + + if (log) Console.WriteLine("\n"); + } + + private static double differenceActuation(double difference) + { + if (difference < 0.75) return 1.0; + if (difference > 0.9) return 0.0; + + return (Math.Cos((difference - 0.75) * Math.PI / 0.15) + 1) / 2; // drops from 1 to 0 as difference increase from 0.75 to 0.9 } private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDifficultyHitObject odho2) { + const double area_coef = 0.7; + OsuHitObject o1 = odho1.BaseObject, o2 = odho2.BaseObject; double distance = Vector2.Distance(o1.StackedPosition, o2.StackedPosition); + double radius = o1.Radius; + + double distance_sqr = distance * distance; + double radius_sqr = radius * radius; - if (distance > o1.Radius * 2) + if (distance > radius * 2) return 0; - if (distance < o1.Radius) - return 1; - return 1 - Math.Pow((distance - o1.Radius) / o1.Radius, 2); + + double s1 = Math.Acos(distance / (2 * radius)) * radius_sqr; // Area of sector + double s2 = distance * Math.Sqrt(radius_sqr - distance_sqr / 4) / 2; // Area of triangle + + double overlappingAreaNormalized = (s1 - s2) * 2 / (Math.PI * radius_sqr); + + const double stack_distance_ratio = 0.1414213562373; + + double perfectStackBuff = (stack_distance_ratio - distance / radius) / stack_distance_ratio; // scale from 0 on normal stack to 1 on perfect stack + perfectStackBuff = Math.Max(perfectStackBuff, 0); // can't be negative + + return overlappingAreaNormalized * area_coef + perfectStackBuff * (1 - area_coef); } private static IEnumerable retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 1126c14cdadc..752cf95b555b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -1,6 +1,7 @@ // 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; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { - public class Reading : Skill + public class Reading : GraphSkill { private readonly List difficulties = new List(); private readonly bool hasHiddenMod; @@ -23,7 +24,23 @@ public Reading(Mod[] mods) hasHiddenMod = mods.Any(m => m is OsuModHidden); } - public override void Process(DifficultyHitObject current) => difficulties.Add(ReadingEvaluator.EvaluateDifficultyOf(current, hasHiddenMod) * skill_multiplier); + public override void Process(DifficultyHitObject current) + { + double currentDifficulty = ReadingEvaluator.EvaluateDifficultyOf(current, hasHiddenMod) * skill_multiplier; + difficulties.Add(currentDifficulty); + + if (current.Index == 0) + CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; + + while (current.StartTime > CurrentSectionEnd) + { + StrainPeaks.Add(CurrentSectionPeak); + CurrentSectionPeak = 0; + CurrentSectionEnd += SectionLength; + } + + CurrentSectionPeak = Math.Max(currentDifficulty, CurrentSectionPeak); + } public override double DifficultyValue() { diff --git a/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs new file mode 100644 index 000000000000..0c05ee30af1e --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Difficulty.Skills +{ + /// + /// A abstract skill with available per objet difficulty. + /// + /// + /// This class should be considered a "processing" class and not persisted. + /// + public abstract class GraphSkill : Skill + { + protected GraphSkill(Mod[] mods) + : base(mods) + { + } + + /// + /// The length of each section. + /// + protected virtual int SectionLength => 400; + + protected double CurrentSectionPeak; // We also keep track of the peak level in the current section. + + protected double CurrentSectionEnd; + + protected readonly List StrainPeaks = new List(); + + /// + /// Returns a live enumerable of the difficulties + /// + public virtual IEnumerable GetCurrentStrainPeaks() => StrainPeaks.Append(CurrentSectionPeak); + } +} diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index b43a272324b9..4696837bb6dc 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -2,7 +2,6 @@ // 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; @@ -13,24 +12,13 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// Used to processes strain values of s, keep track of strain levels caused by the processed objects /// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects. /// - public abstract class StrainSkill : Skill + public abstract class StrainSkill : GraphSkill { /// /// The weight by which each strain value decays. /// protected virtual double DecayWeight => 0.9; - /// - /// The length of each strain section. - /// - protected virtual int SectionLength => 400; - - private double currentSectionPeak; // We also keep track of the peak strain level in the current section. - - private double currentSectionEnd; - - private readonly List strainPeaks = new List(); - protected StrainSkill(Mod[] mods) : base(mods) { @@ -48,16 +36,16 @@ public sealed override void Process(DifficultyHitObject current) { // The first object doesn't generate a strain, so we begin with an incremented section end if (current.Index == 0) - currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; + CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; - while (current.StartTime > currentSectionEnd) + while (current.StartTime > CurrentSectionEnd) { saveCurrentPeak(); - startNewSectionFrom(currentSectionEnd, current); - currentSectionEnd += SectionLength; + startNewSectionFrom(CurrentSectionEnd, current); + CurrentSectionEnd += SectionLength; } - currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak); + CurrentSectionPeak = Math.Max(StrainValueAt(current), CurrentSectionPeak); } /// @@ -65,7 +53,7 @@ public sealed override void Process(DifficultyHitObject current) /// private void saveCurrentPeak() { - strainPeaks.Add(currentSectionPeak); + StrainPeaks.Add(CurrentSectionPeak); } /// @@ -77,7 +65,7 @@ private void startNewSectionFrom(double time, DifficultyHitObject current) { // The maximum strain of the new section is not zero by default // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. - currentSectionPeak = CalculateInitialStrain(time, current); + CurrentSectionPeak = CalculateInitialStrain(time, current); } /// @@ -88,12 +76,6 @@ private void startNewSectionFrom(double time, DifficultyHitObject current) /// The peak strain. protected abstract double CalculateInitialStrain(double time, DifficultyHitObject current); - /// - /// Returns a live enumerable of the peak strains for each section of the beatmap, - /// including the peak of the current section. - /// - public IEnumerable GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak); - /// /// Returns the calculated difficulty value representing all s that have been processed up to this point. /// From 8c2405da68d0c256569bc013f1fef87f8a569773 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 20 Jan 2024 22:59:35 +0200 Subject: [PATCH 32/96] Big bump 1) Fully reworked architecture: splitted reading in branches. 2) Added High AR reading What is broken now: - Low AR (because i focused mostly on High vs Low AR reading values on high end) - HD (it's using live HD rn) - 3 mod speed, cuz part of the speed pp is unaffected by low acc nerf in speed pp calculation --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 87 ++++++---- .../Difficulty/OsuDifficultyAttributes.cs | 18 ++- .../Difficulty/OsuDifficultyCalculator.cs | 24 ++- .../Difficulty/OsuPerformanceCalculator.cs | 73 +++++++-- .../Preprocessing/OsuDifficultyHitObject.cs | 4 +- .../Difficulty/Skills/Reading.cs | 150 +++++++++++++++++- .../Rulesets/Difficulty/Skills/GraphSkill.cs | 2 +- 7 files changed, 299 insertions(+), 59 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 8abc64a135bc..ea415ff5cddb 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -15,22 +15,16 @@ public static class ReadingEvaluator { private const double reading_window_size = 3000; - private const double hidden_multiplier = 0.0; - private const double density_multiplier = 1.0; private const double overlap_multiplier = 0.5; - public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) + public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) { if (current.BaseObject is Spinner || current.Index == 0) return 0; var currObj = (OsuDifficultyHitObject)current; - double currVelocity = currObj.LazyJumpDistance / currObj.StrainTime; - - // Maybe I should just pass in clockrate... - var clockRateEstimate = current.BaseObject.StartTime / currObj.StartTime; - double pastObjectDifficultyInfluence = 1.0; + double pastObjectDifficultyInfluence = 0; double screenOverlapDifficulty = 0; foreach (var loopObj in retrievePastVisibleObjects(currObj)) @@ -40,7 +34,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 90) / 15); - double timeBetweenCurrAndLoopObj = (currObj.BaseObject.StartTime - loopObj.BaseObject.StartTime) / clockRateEstimate; + //double timeBetweenCurrAndLoopObj = (currObj.BaseObject.StartTime - loopObj.BaseObject.StartTime) / clockRateEstimate; + double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime; loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); pastObjectDifficultyInfluence += loopDifficulty; @@ -54,39 +49,60 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd screenOverlapDifficulty += lastOverlapness; } - if (screenOverlapDifficulty > 0) - { - Console.WriteLine($"Object {currObj.StartTime}, overlapness = {screenOverlapDifficulty:0.##}"); - } + //if (screenOverlapDifficulty > 0) + //{ + // Console.WriteLine($"Object {currObj.StartTime}, overlapness = {screenOverlapDifficulty:0.##}"); + //} - double noteDensityDifficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence - 3)), 2.3); + double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence)), 2.3); - double hiddenDifficulty = 0; + screenOverlapDifficulty = Math.Max(0, screenOverlapDifficulty - 0.5); // make overlap value =1 cost significantly less + difficulty *= getConstantAngleNerfFactor(currObj); - if (hidden) - { - double timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; - double timeDifficultyFactor = 1000 / pastObjectDifficultyInfluence; + difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty; - double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15); + // Console.WriteLine($"Object {currObj.StartTime}, {hiddenDifficulty:0.##} + {noteDensityDifficulty:0.##} + {overlapDifficulty:0.##}"); - hiddenDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible / timeDifficultyFactor, 1) + - (8 + visibleObjectFactor) * currVelocity; - } + return difficulty; + } - double difficulty = density_multiplier * noteDensityDifficulty; + public static double EvaluateHighARDifficultyOf(DifficultyHitObject current, bool applyFollowLineAdjust = false) + { + // follow lines make high AR easier, so apply nerf if object isn't new combo + // double adjustedApproachTime = osuCurrObj.Preempt; + // if (applyFollowLineAdjust) adjustedApproachTime += Math.Max(0, (osuCurrObj.FollowLineTime - 200) / 25); - screenOverlapDifficulty = Math.Max(0, screenOverlapDifficulty - 0.5); // make overlap value =1 cost significantly less - difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty; + var currObj = (OsuDifficultyHitObject)current; - difficulty *= getConstantAngleNerfFactor(currObj); + return highArCurve(currObj.Preempt); + } + public static double EvaluateHiddenDifficultyOf(DifficultyHitObject current) + { + var currObj = (OsuDifficultyHitObject)current; - difficulty += hidden_multiplier * hiddenDifficulty; + // Maybe I should just pass in clockrate... + double clockRateEstimate = current.BaseObject.StartTime / currObj.StartTime; + double currVelocity = currObj.LazyJumpDistance / currObj.StrainTime; - // Console.WriteLine($"Object {currObj.StartTime}, {hiddenDifficulty:0.##} + {noteDensityDifficulty:0.##} + {overlapDifficulty:0.##}"); + double hdDifficulty = 0; - return difficulty; + double timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; + + // Apollo version + // double timeDifficultyFactor = pastObjectDifficultyInfluence / 1000; + + double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15); + + hdDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible, 1) + + (8 + visibleObjectFactor) * currVelocity; + + return hdDifficulty; + } + + public static double EvaluatePredictabilityOf(DifficultyHitObject current) + { + return 0; } // Returns a list of objects that are visible on screen at @@ -170,6 +186,17 @@ private static double getTimeNerfFactor(double deltaTime) return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); } + // https://www.desmos.com/calculator/hbj7swzlth + private static double highArCurve(double preempt) + { + double value = Math.Pow(4, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms + value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms) + return value; + } private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); + + // We are using mutiply and divide instead of add and subtract, so values won't be negative + // https://www.desmos.com/calculator/fv5xerwpd2 + 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); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index b977247bfcfd..b646469bcb7e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -39,10 +39,22 @@ public class OsuDifficultyAttributes : DifficultyAttributes public double FlashlightDifficulty { get; set; } /// - /// The difficulty corresponding to the reading skill. + /// The difficulty corresponding to the reading skill. Low AR branch. /// - [JsonProperty("reading_difficulty")] - public double ReadingDifficulty { get; set; } + [JsonProperty("reading_low_ar_difficulty")] + public double ReadingDifficultyLowAR { get; set; } + + /// + /// The difficulty corresponding to the reading skill. High AR branch. + /// + [JsonProperty("reading_high_ar_difficulty")] + public double ReadingDifficultyHighAR { get; set; } + + /// + /// The difficulty corresponding to the reading skill. Hidden mod branch. + /// + [JsonProperty("reading_hidden_difficulty")] + public double ReadingDifficultyHidden { get; set; } /// /// Describes how much of is contributed to by hitcircles or sliders. diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index e956da37d2cf..414e95097af2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -25,7 +25,7 @@ public class OsuDifficultyCalculator : DifficultyCalculator private const double difficulty_multiplier = 0.0675; public override int Version => 20220902; - public double SumPower => 1.1; + public static double SumPower => 1.1; public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -42,7 +42,10 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; double speedNotes = ((Speed)skills[2]).RelevantNoteCount(); double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; - double readingRating = Math.Sqrt(skills[4].DifficultyValue()) * difficulty_multiplier; + + double readingLowARRating = Math.Sqrt(skills[4].DifficultyValue()) * difficulty_multiplier; + double readingHighARRating = Math.Sqrt(skills[5].DifficultyValue()) * difficulty_multiplier; + double readingHiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; @@ -66,14 +69,19 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat if (mods.Any(h => h is OsuModFlashlight)) baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; - double baseReadingPerformance = Math.Pow(readingRating, 2.0) * 25.0; + double baseReadingLowARPerformance = Math.Pow(readingLowARRating, 2.0) * 25.0; + double baseReadingHighARPerformance = Math.Pow(5 * Math.Max(1, readingHighARRating / 0.0675) - 4, 3) / 100000; + double baseReadingHiddenPerformance = Math.Pow(readingHiddenRating, 2.0) * 25.0; double basePerformance = Math.Pow( Math.Pow(baseAimPerformance, SumPower) + Math.Pow(baseSpeedPerformance, SumPower) + Math.Pow(baseFlashlightPerformance, SumPower) + - Math.Pow(baseReadingPerformance, SumPower), 1.0 / SumPower + Math.Pow(baseReadingLowARPerformance, SumPower) + + Math.Pow(baseReadingHighARPerformance, SumPower) + + Math.Pow(baseReadingHiddenPerformance, SumPower) + , 1.0 / SumPower ); double starRating = basePerformance > 0.00001 @@ -101,7 +109,9 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat SpeedDifficulty = speedRating, SpeedNoteCount = speedNotes, FlashlightDifficulty = flashlightRating, - ReadingDifficulty = readingRating, + ReadingDifficultyLowAR = readingLowARRating, + ReadingDifficultyHighAR = readingHighARRating, + ReadingDifficultyHidden = readingHiddenRating, SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, @@ -138,7 +148,9 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo new Aim(mods, false), new Speed(mods), new Flashlight(mods), - new Reading(mods), + new ReadingLowAR(mods), + new ReadingHighAR(mods), + new ReadingHidden(mods), }; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 9c27806221c9..1da3e11318c0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -15,6 +15,8 @@ public class OsuPerformanceCalculator : PerformanceCalculator { public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. + public static double SumPower => 1.1; // Maybe it should just use OsuDifficultyCalculator SumPower + private double accuracy; private int scoreMaxCombo; private int countGreat; @@ -68,11 +70,11 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double readingValue = computeReadingValue(score, osuAttributes); double totalValue = Math.Pow( - Math.Pow(aimValue, 1.1) + - Math.Pow(speedValue, 1.1) + - Math.Pow(accuracyValue, 1.1) + - Math.Pow(flashlightValue, 1.1) + - Math.Pow(readingValue, 1.1), 1.0 / 1.1 + Math.Pow(aimValue, SumPower) + + Math.Pow(speedValue, SumPower) + + Math.Pow(accuracyValue, SumPower) + + Math.Pow(flashlightValue, SumPower) + + Math.Pow(readingValue, SumPower), 1.0 / SumPower ) * multiplier; return new OsuPerformanceAttributes @@ -102,8 +104,8 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut aimValue *= getComboScalingFactor(attributes); double approachRateFactor = 0.0; - if (attributes.ApproachRate > 10.33) - approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); + //if (attributes.ApproachRate > 10.33) + // approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); // for testing //else if (attributes.ApproachRate < 8.0) // approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate); @@ -157,8 +159,8 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib speedValue *= getComboScalingFactor(attributes); double approachRateFactor = 0.0; - if (attributes.ApproachRate > 10.33) - approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); + //if (attributes.ApproachRate > 10.33) + // approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); speedValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. @@ -255,12 +257,28 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a private double computeReadingValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double rawReading = attributes.ReadingDifficulty; + // Taking the highest value + double readingARValue = Math.Max(computeReadingLowARValue(score, attributes), computeReadingHighARValue(score, attributes)); + double readingHDValue = computeReadingHiddenValue(score, attributes); + // Here would be also readingSliderValue + + double totalReadingValue = + Math.Pow( + Math.Pow(readingARValue, SumPower) + + Math.Pow(readingHDValue, SumPower), 1.0 / SumPower); + + return totalReadingValue; + } + + private double computeReadingLowARValue(ScoreInfo score, OsuDifficultyAttributes attributes) + { + double rawReading = attributes.ReadingDifficultyLowAR; if (score.Mods.Any(m => m is OsuModTouchDevice)) rawReading = Math.Pow(rawReading, 0.8); - double readingValue = Math.Pow(rawReading, 2.0) * 25.0; + // double readingValue = Math.Pow(rawReading, 2.0) * 25.0; + double readingValue = Math.Pow(rawReading, 2.0) * 25.0; // I need to change the curve // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) @@ -268,14 +286,43 @@ private double computeReadingValue(ScoreInfo score, OsuDifficultyAttributes attr readingValue *= getComboScalingFactor(attributes); - // Scale the reading value with accuracy _harshly_. + // Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework. readingValue *= accuracy * accuracy; // It is important to also consider accuracy difficulty when doing that. - readingValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; + readingValue *= Math.Pow(0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500, 2); return readingValue; } + private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttributes attributes) + { + // Copied from aim + double highARValue = Math.Pow(5.0 * Math.Max(1.0, attributes.ReadingDifficultyHighAR / 0.0675) - 4.0, 3.0) / 100000.0; + + // High AR should have length bonus, even more agressive than normal aim + double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); + highARValue *= lengthBonus * lengthBonus; + + // 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); + + // Same as Aim + highARValue *= accuracy; + // It is important to consider accuracy difficulty when scaling with accuracy. + highARValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; + + return highARValue; + } + + private double computeReadingHiddenValue(ScoreInfo score, OsuDifficultyAttributes attributes) + { + return 0; + } + private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) { // Guess the number of misses + slider breaks from combo diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 58f613f961eb..3f5bef4fb144 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -93,6 +93,7 @@ public class OsuDifficultyHitObject : DifficultyHitObject private readonly OsuHitObject? lastLastObject; private readonly OsuHitObject lastObject; public readonly double Preempt; + public readonly double FollowLineTime; public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject? lastLastObject, double clockRate, List objects, int index) : base(hitObject, lastObject, clockRate, objects, index) @@ -183,7 +184,7 @@ private static double differenceActuation(double difference) private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDifficultyHitObject odho2) { - const double area_coef = 0.7; + const double area_coef = 0.8; OsuHitObject o1 = odho1.BaseObject, o2 = odho2.BaseObject; @@ -201,6 +202,7 @@ private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDiff double overlappingAreaNormalized = (s1 - s2) * 2 / (Math.PI * radius_sqr); + // don't ask me how i get this value, looks oddly similar to PI - 3 const double stack_distance_ratio = 0.1414213562373; double perfectStackBuff = (stack_distance_ratio - distance / radius) / stack_distance_ratio; // scale from 0 on normal stack to 1 on perfect stack diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 752cf95b555b..e48c72ce6979 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -8,25 +8,26 @@ using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { - public class Reading : GraphSkill + + public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - private readonly bool hasHiddenMod; private const double skill_multiplier = 2.4; - public Reading(Mod[] mods) + public ReadingLowAR(Mod[] mods) : base(mods) { - hasHiddenMod = mods.Any(m => m is OsuModHidden); } public override void Process(DifficultyHitObject current) { - double currentDifficulty = ReadingEvaluator.EvaluateDifficultyOf(current, hasHiddenMod) * skill_multiplier; + double currentDifficulty = ReadingEvaluator.EvaluateDensityDifficultyOf(current) * skill_multiplier; + difficulties.Add(currentDifficulty); if (current.Index == 0) @@ -62,4 +63,143 @@ public override double DifficultyValue() return difficulty; } } + + public class ReadingHighAR : GraphSkill + { + public ReadingHighAR(Mod[] mods) + : base(mods) + { + aimComponent = new HighARAimComponent(mods); + speedComponent = new HighARSpeedComponent(mods); + } + + private HighARAimComponent aimComponent; + private HighARSpeedComponent speedComponent; + + private readonly List difficulties = new List(); + + public override void Process(DifficultyHitObject current) + { + aimComponent.Process(current); + speedComponent.Process(current); + + double power = OsuDifficultyCalculator.SumPower; + double mergedDifficulty = Math.Pow( + Math.Pow(aimComponent.CurrentSectionPeak, power) + + Math.Pow(speedComponent.CurrentSectionPeak, power), 1.0 / power); + + difficulties.Add(mergedDifficulty); + + if (current.Index == 0) + CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; + + while (current.StartTime > CurrentSectionEnd) + { + StrainPeaks.Add(CurrentSectionPeak); + CurrentSectionPeak = 0; + CurrentSectionEnd += SectionLength; + } + + CurrentSectionPeak = Math.Max(mergedDifficulty, CurrentSectionPeak); + } + public override double DifficultyValue() + { + double power = OsuDifficultyCalculator.SumPower; + return Math.Pow( + Math.Pow(aimComponent.DifficultyValue(), power) + + Math.Pow(speedComponent.DifficultyValue(), power), 1.0 / power); + } + } + + public class HighARAimComponent : OsuStrainSkill + { + public HighARAimComponent(Mod[] mods) + : base(mods) + { + } + + private double currentStrain; + // private double currentRhythm; + + private double skillMultiplier => 12; + private double strainDecayBase => 0.15; + + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime); + + protected override double StrainValueAt(DifficultyHitObject current) + { + currentStrain *= strainDecay(current.DeltaTime); + + double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true); + aimDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current); + currentStrain += aimDifficulty * skillMultiplier; + + // currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); + + double totalStrain = currentStrain; + return totalStrain; + } + } + + public class HighARSpeedComponent : OsuStrainSkill + { + private double skillMultiplier => 900; + private double strainDecayBase => 0.3; + + private double currentStrain; + private double currentRhythm; + + public HighARSpeedComponent(Mod[] mods) + : base(mods) + { + } + + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * strainDecay(time - current.Previous(0).StartTime); + + protected override double StrainValueAt(DifficultyHitObject current) + { + currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); + + double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; + speedDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current); + currentStrain += speedDifficulty; + + currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); + // currentRhythm *= currentRhythm; // Squaring is broken cuz rhythm is broken (((( + + double totalStrain = currentStrain * currentRhythm; + return totalStrain; + } + } + + public class ReadingHidden : OsuStrainSkill + { + public ReadingHidden(Mod[] mods) + : base(mods) + { + } + + private double currentStrain; + + private double skillMultiplier => 5; + private double strainDecayBase => 0.15; + + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime); + + protected override double StrainValueAt(DifficultyHitObject current) + { + currentStrain *= strainDecay(current.DeltaTime); + + double hdDifficulty = 0; + + currentStrain += hdDifficulty * skillMultiplier; + return currentStrain; + } + } } diff --git a/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs index 0c05ee30af1e..953abd61fe33 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs @@ -25,7 +25,7 @@ protected GraphSkill(Mod[] mods) /// protected virtual int SectionLength => 400; - protected double CurrentSectionPeak; // We also keep track of the peak level in the current section. + public double CurrentSectionPeak { get; protected set; } // We also keep track of the peak level in the current section. protected double CurrentSectionEnd; From f429f213d97b521d70ceefee8e5baa97d41b3dc7 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 24 Jan 2024 01:07:44 +0200 Subject: [PATCH 33/96] Fixed low AR HD is still not existant Also, small try to buff high AR on low SR (bad rn) --- .../Difficulty/Evaluators/AimEvaluator.cs | 13 ++++++++++++- .../Difficulty/Evaluators/ReadingEvaluator.cs | 2 +- .../Difficulty/OsuDifficultyCalculator.cs | 2 +- .../Difficulty/OsuPerformanceCalculator.cs | 8 +++----- osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs | 8 ++++---- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs index 3d1939acac5f..118468cce6ef 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs @@ -24,7 +24,7 @@ public static class AimEvaluator /// and slider difficulty. /// /// - public static double EvaluateDifficultyOf(DifficultyHitObject current, bool withSliderTravelDistance) + public static double EvaluateDifficultyOf(DifficultyHitObject current, bool withSliderTravelDistance, double clampPreemptTime = 0) { if (current.BaseObject is Spinner || current.Index <= 1 || current.Previous(0).BaseObject is Spinner) return 0; @@ -121,6 +121,17 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with // Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger. aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier); + if (clampPreemptTime > 0) + { + // Scale if AR is too high for high AR calc + double multiplier = osuCurrObj.StrainTime / Math.Min(osuCurrObj.StrainTime, clampPreemptTime - 150); // 150ms is considered as reaction time + double multiplierIfAR11 = osuCurrObj.StrainTime / Math.Min(osuCurrObj.StrainTime, 150); + + multiplier = Math.Min(multiplier, multiplierIfAR11); + + aimStrain *= multiplier; + } + // Add in additional slider velocity bonus. if (withSliderTravelDistance) aimStrain += sliderBonus * slider_multiplier; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index ea415ff5cddb..468b9adc911a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -15,7 +15,7 @@ public static class ReadingEvaluator { private const double reading_window_size = 3000; - private const double overlap_multiplier = 0.5; + private const double overlap_multiplier = 1; public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 414e95097af2..bdafcd19b185 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -69,7 +69,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat if (mods.Any(h => h is OsuModFlashlight)) baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; - double baseReadingLowARPerformance = Math.Pow(readingLowARRating, 2.0) * 25.0; + double baseReadingLowARPerformance = Math.Pow(readingLowARRating, 2.5) * 17.0; double baseReadingHighARPerformance = Math.Pow(5 * Math.Max(1, readingHighARRating / 0.0675) - 4, 3) / 100000; double baseReadingHiddenPerformance = Math.Pow(readingHiddenRating, 2.0) * 25.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 1da3e11318c0..aa6428a1bd1d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -257,7 +257,7 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a private double computeReadingValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - // Taking the highest value + // Taking the highest value for AR reading double readingARValue = Math.Max(computeReadingLowARValue(score, attributes), computeReadingHighARValue(score, attributes)); double readingHDValue = computeReadingHiddenValue(score, attributes); // Here would be also readingSliderValue @@ -277,8 +277,7 @@ private double computeReadingLowARValue(ScoreInfo score, OsuDifficultyAttributes if (score.Mods.Any(m => m is OsuModTouchDevice)) rawReading = Math.Pow(rawReading, 0.8); - // double readingValue = Math.Pow(rawReading, 2.0) * 25.0; - double readingValue = Math.Pow(rawReading, 2.0) * 25.0; // I need to change the curve + double readingValue = Math.Pow(rawReading, 2.5) * 17.0; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) @@ -310,8 +309,7 @@ private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttribute highARValue *= getComboScalingFactor(attributes); - // Same as Aim - highARValue *= accuracy; + highARValue *= accuracy * accuracy; // It is important to consider accuracy difficulty when scaling with accuracy. highARValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index e48c72ce6979..0a97b0f0be58 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -9,7 +9,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -17,7 +16,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - private const double skill_multiplier = 2.4; + //private double skillMultiplier => 5.5; + private double skillMultiplier => 2.3; public ReadingLowAR(Mod[] mods) : base(mods) @@ -26,7 +26,7 @@ public ReadingLowAR(Mod[] mods) public override void Process(DifficultyHitObject current) { - double currentDifficulty = ReadingEvaluator.EvaluateDensityDifficultyOf(current) * skill_multiplier; + double currentDifficulty = ReadingEvaluator.EvaluateDensityDifficultyOf(current) * skillMultiplier; difficulties.Add(currentDifficulty); @@ -132,7 +132,7 @@ protected override double StrainValueAt(DifficultyHitObject current) { currentStrain *= strainDecay(current.DeltaTime); - double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true); + double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true, ((OsuDifficultyHitObject)current).Preempt); aimDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current); currentStrain += aimDifficulty * skillMultiplier; From 77ce1aef42008856b4b8882a562242f0fb9461be Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 24 Jan 2024 01:51:13 +0200 Subject: [PATCH 34/96] Slight balancing Added diffspike nerf to reading (accounting to memorization of one hard part) --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 2 +- .../Difficulty/OsuDifficultyCalculator.cs | 1 + .../Difficulty/OsuPerformanceCalculator.cs | 5 ++++- osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs | 13 +++++++++++-- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 468b9adc911a..ac7d3ecfaf2a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -15,7 +15,7 @@ public static class ReadingEvaluator { private const double reading_window_size = 3000; - private const double overlap_multiplier = 1; + private const double overlap_multiplier = 0.8; public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index bdafcd19b185..ea5c461c714b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -71,6 +71,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseReadingLowARPerformance = Math.Pow(readingLowARRating, 2.5) * 17.0; double baseReadingHighARPerformance = Math.Pow(5 * Math.Max(1, readingHighARRating / 0.0675) - 4, 3) / 100000; + double baseReadingHiddenPerformance = Math.Pow(readingHiddenRating, 2.0) * 25.0; double basePerformance = diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index aa6428a1bd1d..9ba2bb595802 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -258,7 +258,10 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a private double computeReadingValue(ScoreInfo score, OsuDifficultyAttributes attributes) { // Taking the highest value for AR reading - double readingARValue = Math.Max(computeReadingLowARValue(score, attributes), computeReadingHighARValue(score, attributes)); + //double readingARValue = Math.Max(computeReadingLowARValue(score, attributes), computeReadingHighARValue(score, attributes)); + double readingARValue = Math.Pow( + Math.Pow(computeReadingLowARValue(score, attributes), SumPower) + + Math.Pow(computeReadingHighARValue(score, attributes), SumPower), 1.0 / SumPower); double readingHDValue = computeReadingHiddenValue(score, attributes); // Here would be also readingSliderValue diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 0a97b0f0be58..91cd7c9b0d6d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Utils; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; @@ -43,6 +44,8 @@ public override void Process(DifficultyHitObject current) CurrentSectionPeak = Math.Max(currentDifficulty, CurrentSectionPeak); } + private double reducedNoteCount => 5; + private double reducedNoteBaseline => 0.7; public override double DifficultyValue() { double difficulty = 0; @@ -53,6 +56,12 @@ public override double DifficultyValue() List values = peaks.OrderByDescending(d => d).ToList(); + for (int i = 0; i < Math.Min(values.Count, reducedNoteCount); i++) + { + double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp(i / reducedNoteCount, 0, 1))); + values[i] *= Interpolation.Lerp(reducedNoteBaseline, 1.0, scale); + } + // 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++) @@ -121,7 +130,7 @@ public HighARAimComponent(Mod[] mods) private double currentStrain; // private double currentRhythm; - private double skillMultiplier => 12; + private double skillMultiplier => 13; private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); @@ -145,7 +154,7 @@ protected override double StrainValueAt(DifficultyHitObject current) public class HighARSpeedComponent : OsuStrainSkill { - private double skillMultiplier => 900; + private double skillMultiplier => 650; private double strainDecayBase => 0.3; private double currentStrain; From f6df24779d33da28b3ecb59689a060a1569a362b Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 25 Jan 2024 01:21:11 +0200 Subject: [PATCH 35/96] Added infra and hidden HD is now ported from Apollo, but i'm planning to change it Also, some database-related stuff addings --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 39 +++++---- .../Difficulty/OsuDifficultyAttributes.cs | 36 +++++++-- .../Difficulty/OsuDifficultyCalculator.cs | 27 ++++--- .../Difficulty/OsuPerformanceAttributes.cs | 10 +-- .../Difficulty/OsuPerformanceCalculator.cs | 80 ++++++++++++------- .../Preprocessing/OsuDifficultyHitObject.cs | 12 ++- .../Difficulty/Skills/Reading.cs | 53 +++++++++--- .../Difficulty/Skills/Speed.cs | 7 +- .../Difficulty/DifficultyAttributes.cs | 4 + 9 files changed, 182 insertions(+), 86 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index ac7d3ecfaf2a..cd5c8e52edb6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -17,15 +17,9 @@ public static class ReadingEvaluator private const double overlap_multiplier = 0.8; - public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) + private static double calculateDenstityOf(OsuDifficultyHitObject currObj) { - if (current.BaseObject is Spinner || current.Index == 0) - return 0; - - var currObj = (OsuDifficultyHitObject)current; - double pastObjectDifficultyInfluence = 0; - double screenOverlapDifficulty = 0; foreach (var loopObj in retrievePastVisibleObjects(currObj)) { @@ -39,7 +33,17 @@ public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); pastObjectDifficultyInfluence += loopDifficulty; + } + + return pastObjectDifficultyInfluence; + } + + private static double calculateOverlapDifficultyOf(OsuDifficultyHitObject currObj) + { + double screenOverlapDifficulty = 0; + foreach (var loopObj in retrievePastVisibleObjects(currObj)) + { double lastOverlapness = 0; foreach (var overlapObj in loopObj.OverlapObjects) { @@ -49,10 +53,17 @@ public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) screenOverlapDifficulty += lastOverlapness; } - //if (screenOverlapDifficulty > 0) - //{ - // Console.WriteLine($"Object {currObj.StartTime}, overlapness = {screenOverlapDifficulty:0.##}"); - //} + return screenOverlapDifficulty; + } + public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) + { + if (current.BaseObject is Spinner || current.Index == 0) + return 0; + + var currObj = (OsuDifficultyHitObject)current; + + double pastObjectDifficultyInfluence = calculateDenstityOf(currObj); + double screenOverlapDifficulty = calculateOverlapDifficultyOf(currObj); double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence)), 2.3); @@ -88,13 +99,11 @@ public static double EvaluateHiddenDifficultyOf(DifficultyHitObject current) double hdDifficulty = 0; double timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; - - // Apollo version - // double timeDifficultyFactor = pastObjectDifficultyInfluence / 1000; + double timeDifficultyFactor = calculateDenstityOf(currObj) / 1000; double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15); - hdDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible, 1) + + hdDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible * timeDifficultyFactor, 1) + (8 + visibleObjectFactor) * currVelocity; return hdDifficulty; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index b646469bcb7e..09424e741ae7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -32,12 +32,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes [JsonProperty("speed_note_count")] public double SpeedNoteCount { get; set; } - /// - /// The difficulty corresponding to the flashlight skill. - /// - [JsonProperty("flashlight_difficulty")] - public double FlashlightDifficulty { get; set; } - /// /// The difficulty corresponding to the reading skill. Low AR branch. /// @@ -50,11 +44,23 @@ public class OsuDifficultyAttributes : DifficultyAttributes [JsonProperty("reading_high_ar_difficulty")] public double ReadingDifficultyHighAR { get; set; } + /// + /// The difficulty corresponding to the reading skill. Sliders branch. + /// + [JsonProperty("reading_sliders_difficulty")] + public double ReadingDifficultySliders { get; set; } + /// /// The difficulty corresponding to the reading skill. Hidden mod branch. /// [JsonProperty("reading_hidden_difficulty")] - public double ReadingDifficultyHidden { get; set; } + public double HiddenDifficulty { get; set; } + + /// + /// The difficulty corresponding to the flashlight skill. + /// + [JsonProperty("flashlight_difficulty")] + public double FlashlightDifficulty { get; set; } /// /// Describes how much of is contributed to by hitcircles or sliders. @@ -118,6 +124,12 @@ public class OsuDifficultyAttributes : DifficultyAttributes yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); yield return (ATTRIB_ID_SPEED_NOTE_COUNT, SpeedNoteCount); + yield return (ATTRIB_ID_READING_LOW_AR, ReadingDifficultyLowAR); + yield return (ATTRIB_ID_READING_HIGH_AR, ReadingDifficultyHighAR); + yield return (ATTRIB_ID_READING_SLIDERS, ReadingDifficultySliders); + + if (ShouldSerializeHiddenDifficulty()) + yield return (ATTRIB_ID_READING_HIDDEN, HiddenDifficulty); } public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) @@ -129,10 +141,15 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary val OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY]; ApproachRate = values[ATTRIB_ID_APPROACH_RATE]; StarRating = values[ATTRIB_ID_DIFFICULTY]; - FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT]; + ReadingDifficultyLowAR = values[ATTRIB_ID_READING_LOW_AR]; + ReadingDifficultyHighAR = values[ATTRIB_ID_READING_HIGH_AR]; + ReadingDifficultySliders = values[ATTRIB_ID_READING_SLIDERS]; + HiddenDifficulty = values.GetValueOrDefault(ATTRIB_ID_READING_HIDDEN); + FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); + DrainRate = onlineInfo.DrainRate; HitCircleCount = onlineInfo.CircleCount; SliderCount = onlineInfo.SliderCount; @@ -148,6 +165,9 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary val [UsedImplicitly] public bool ShouldSerializeFlashlightDifficulty() => Mods.Any(m => m is ModFlashlight); + [UsedImplicitly] + public bool ShouldSerializeHiddenDifficulty() => Mods.Any(m => m is ModHidden); + #endregion } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index ea5c461c714b..96fffc346457 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -45,7 +45,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double readingLowARRating = Math.Sqrt(skills[4].DifficultyValue()) * difficulty_multiplier; double readingHighARRating = Math.Sqrt(skills[5].DifficultyValue()) * difficulty_multiplier; - double readingHiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * difficulty_multiplier; + double readingSlidersRating = 0; + double hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; @@ -64,24 +65,31 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; - double baseFlashlightPerformance = 0.0; + // Cognition + double baseFlashlightPerformance = 0.0; if (mods.Any(h => h is OsuModFlashlight)) baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; double baseReadingLowARPerformance = Math.Pow(readingLowARRating, 2.5) * 17.0; double baseReadingHighARPerformance = Math.Pow(5 * Math.Max(1, readingHighARRating / 0.0675) - 4, 3) / 100000; + double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SumPower) + Math.Pow(baseReadingHighARPerformance, SumPower), 1.0 / SumPower); + + double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, 1.8) + Math.Pow(baseReadingARPerformance, 1.8), 1.0 / 1.8); - double baseReadingHiddenPerformance = Math.Pow(readingHiddenRating, 2.0) * 25.0; + double baseReadingHiddenPerformance = 0; + if (mods.Any(h => h is OsuModHidden)) + baseReadingHiddenPerformance = Math.Pow(hiddenRating, 2.0) * 25.0; + + double baseReadingSliderPerformance = 0; + double baseReadingNonARPerformance = baseReadingHiddenPerformance + baseReadingSliderPerformance; double basePerformance = Math.Pow( Math.Pow(baseAimPerformance, SumPower) + Math.Pow(baseSpeedPerformance, SumPower) + - Math.Pow(baseFlashlightPerformance, SumPower) + - Math.Pow(baseReadingLowARPerformance, SumPower) + - Math.Pow(baseReadingHighARPerformance, SumPower) + - Math.Pow(baseReadingHiddenPerformance, SumPower) + Math.Pow(baseFlashlightARPerformance, SumPower) + + Math.Pow(baseReadingNonARPerformance, SumPower) , 1.0 / SumPower ); @@ -109,10 +117,11 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat AimDifficulty = aimRating, SpeedDifficulty = speedRating, SpeedNoteCount = speedNotes, - FlashlightDifficulty = flashlightRating, ReadingDifficultyLowAR = readingLowARRating, ReadingDifficultyHighAR = readingHighARRating, - ReadingDifficultyHidden = readingHiddenRating, + ReadingDifficultySliders = readingSlidersRating, + HiddenDifficulty = hiddenRating, + FlashlightDifficulty = flashlightRating, SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 3a11ea88fe1a..5616ae72e440 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -18,11 +18,8 @@ public class OsuPerformanceAttributes : PerformanceAttributes [JsonProperty("accuracy")] public double Accuracy { get; set; } - [JsonProperty("flashlight")] - public double Flashlight { get; set; } - - [JsonProperty("reading")] - public double Reading { get; set; } + [JsonProperty("cognition")] + public double Cognition { get; set; } [JsonProperty("effective_miss_count")] public double EffectiveMissCount { get; set; } @@ -35,8 +32,7 @@ public override IEnumerable GetAttributesForDisplay yield return new PerformanceDisplayAttribute(nameof(Aim), "Aim", Aim); yield return new PerformanceDisplayAttribute(nameof(Speed), "Speed", Speed); yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); - yield return new PerformanceDisplayAttribute(nameof(Flashlight), "Flashlight Bonus", Flashlight); - yield return new PerformanceDisplayAttribute(nameof(Reading), "Reading", Reading); + yield return new PerformanceDisplayAttribute(nameof(Cognition), "Cognition", Cognition); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 9ba2bb595802..71815a0415c3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -66,15 +66,23 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double aimValue = computeAimValue(score, osuAttributes); double speedValue = computeSpeedValue(score, osuAttributes); double accuracyValue = computeAccuracyValue(score, osuAttributes); + + // Cognition + double flashlightValue = computeFlashlightValue(score, osuAttributes); - double readingValue = computeReadingValue(score, osuAttributes); + double readingARValue = computeReadingARValue(score, osuAttributes); + // Reduce AR reading bonus if FL is present + double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, 1.8) + Math.Pow(readingARValue, 1.8), 1.0 / 1.8); + + double readingNonARValue = computeReadingNonARValue(score, osuAttributes); + double cognitionValue = Math.Pow(Math.Pow(flashlightARValue, SumPower) + Math.Pow(readingNonARValue, SumPower), 1.0 / SumPower); + double totalValue = Math.Pow( Math.Pow(aimValue, SumPower) + Math.Pow(speedValue, SumPower) + Math.Pow(accuracyValue, SumPower) + - Math.Pow(flashlightValue, SumPower) + - Math.Pow(readingValue, SumPower), 1.0 / SumPower + Math.Pow(cognitionValue, SumPower), 1.0 / SumPower ) * multiplier; return new OsuPerformanceAttributes @@ -82,8 +90,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s Aim = aimValue, Speed = speedValue, Accuracy = accuracyValue, - Flashlight = flashlightValue, - Reading = readingValue, + Cognition = cognitionValue, EffectiveMissCount = effectiveMissCount, Total = totalValue }; @@ -118,11 +125,11 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); // for testing - else if (score.Mods.Any(h => h is OsuModHidden)) - { - // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); - } + //else if (score.Mods.Any(h => h is OsuModHidden)) + //{ + // // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. + // aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); + //} // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = attributes.SliderCount * 0.15; @@ -170,11 +177,11 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib speedValue *= 1.12; } // for testing - else if (score.Mods.Any(m => m is OsuModHidden)) - { - // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); - } + //else if (score.Mods.Any(m => m is OsuModHidden)) + //{ + // // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. + // speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); + //} // Calculate accuracy assuming the worst case scenario double relevantTotalDiff = totalHits - attributes.SpeedNoteCount; @@ -221,8 +228,8 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; // For testing - else if (score.Mods.Any(m => m is OsuModHidden)) - accuracyValue *= 1.08; + //else if (score.Mods.Any(m => m is OsuModHidden)) + // accuracyValue *= 1.08; if (score.Mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; @@ -255,22 +262,22 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a return flashlightValue; } - private double computeReadingValue(ScoreInfo score, OsuDifficultyAttributes attributes) + private double computeReadingARValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - // Taking the highest value for AR reading //double readingARValue = Math.Max(computeReadingLowARValue(score, attributes), computeReadingHighARValue(score, attributes)); - double readingARValue = Math.Pow( + double readingValue = Math.Pow( Math.Pow(computeReadingLowARValue(score, attributes), SumPower) + Math.Pow(computeReadingHighARValue(score, attributes), SumPower), 1.0 / SumPower); - double readingHDValue = computeReadingHiddenValue(score, attributes); - // Here would be also readingSliderValue - double totalReadingValue = - Math.Pow( - Math.Pow(readingARValue, SumPower) + - Math.Pow(readingHDValue, SumPower), 1.0 / SumPower); + return readingValue; + } + + private double computeReadingNonARValue(ScoreInfo score, OsuDifficultyAttributes attributes) + { + double readingHDValue = computeReadingHiddenValue(score, attributes); + double readingSlidersValue = 0; - return totalReadingValue; + return readingHDValue + readingSlidersValue; } private double computeReadingLowARValue(ScoreInfo score, OsuDifficultyAttributes attributes) @@ -321,7 +328,24 @@ private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttribute private double computeReadingHiddenValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - return 0; + if (!score.Mods.Any(h => h is OsuModHidden)) + return 0.0; + + double rawReading = attributes.HiddenDifficulty; + double readingValue = Math.Pow(rawReading, 2.0) * 25.0; + + // 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); + + // Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework. + readingValue *= accuracy * accuracy; + // It is important to also consider accuracy difficulty when doing that. + readingValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; + + return readingValue; } private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 3f5bef4fb144..06bae08ab8d6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -4,10 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Channels; using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Difficulty.Evaluators; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -86,7 +86,12 @@ public class OsuDifficultyHitObject : DifficultyHitObject public double HitWindowGreat { get; private set; } /// - /// Objects that was visible after the note was hit together with cumulative overlapping difficulty. + /// Rhythm difficulty of the object. Saved for optimization, rhythm calculation is very expensive. + /// + public double RhythmDifficulty { get; private set; } + + /// + /// Objects that was visible after the note was hit together with cumulative overlapping difficulty. Saved for optimization to avoid O(x^4) time complexity. /// public IList OverlapObjects { get; private set; } @@ -116,6 +121,8 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje setDistances(clockRate); + RhythmDifficulty = RhythmEvaluator.EvaluateDifficultyOf(this); + OverlapObjects = new List(); double totalOverlapnessDifficulty = 0; @@ -126,7 +133,6 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje OsuDifficultyHitObject prevObject = this; bool log = false; - if (log) Console.WriteLine($"Checking for object {hitObject.StartTime}"); foreach (var loopObj in retrieveCurrentVisibleObjects(this)) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 91cd7c9b0d6d..a60dbb3b9690 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -62,6 +62,8 @@ public override double DifficultyValue() values[i] *= Interpolation.Lerp(reducedNoteBaseline, 1.0, scale); } + values = values.OrderByDescending(d => d).ToList(); + // 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++) @@ -171,13 +173,15 @@ public HighARSpeedComponent(Mod[] mods) protected override double StrainValueAt(DifficultyHitObject current) { - currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); + OsuDifficultyHitObject currODHO = (OsuDifficultyHitObject)current; + + currentStrain *= strainDecay(currODHO.StrainTime); double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; speedDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current); currentStrain += speedDifficulty; - currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); + currentRhythm = currODHO.RhythmDifficulty; // currentRhythm *= currentRhythm; // Squaring is broken cuz rhythm is broken (((( double totalStrain = currentStrain * currentRhythm; @@ -185,30 +189,53 @@ protected override double StrainValueAt(DifficultyHitObject current) } } - public class ReadingHidden : OsuStrainSkill + public class ReadingHidden : GraphSkill { public ReadingHidden(Mod[] mods) : base(mods) { } - private double currentStrain; + private readonly List difficulties = new List(); + private double skillMultiplier => 2.3; - private double skillMultiplier => 5; - private double strainDecayBase => 0.15; + public override void Process(DifficultyHitObject current) + { + double currentDifficulty = ReadingEvaluator.EvaluateHiddenDifficultyOf(current) * skillMultiplier; - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + difficulties.Add(currentDifficulty); - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime); + if (current.Index == 0) + CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; - protected override double StrainValueAt(DifficultyHitObject current) + while (current.StartTime > CurrentSectionEnd) + { + StrainPeaks.Add(CurrentSectionPeak); + CurrentSectionPeak = 0; + CurrentSectionEnd += SectionLength; + } + + CurrentSectionPeak = Math.Max(currentDifficulty, CurrentSectionPeak); + } + + public override double DifficultyValue() { - currentStrain *= strainDecay(current.DeltaTime); + 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); + + List values = peaks.OrderByDescending(d => d).ToList(); - double hdDifficulty = 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); + } - currentStrain += hdDifficulty * skillMultiplier; - return currentStrain; + return difficulty; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 40aac013ab8a..edc3302b09ee 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -38,11 +38,12 @@ public Speed(Mod[] mods) protected override double StrainValueAt(DifficultyHitObject current) { - currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); - currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; + OsuDifficultyHitObject currODHO = (OsuDifficultyHitObject)current; - currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); + currentStrain *= strainDecay(currODHO.StrainTime); + currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; + currentRhythm = currODHO.RhythmDifficulty; double totalStrain = currentStrain * currentRhythm; objectStrains.Add(totalStrain); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 9690924b1c46..b07ade01debe 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -26,6 +26,10 @@ public class DifficultyAttributes protected const int ATTRIB_ID_FLASHLIGHT = 17; protected const int ATTRIB_ID_SLIDER_FACTOR = 19; protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21; + protected const int ATTRIB_ID_READING_LOW_AR = 23; + protected const int ATTRIB_ID_READING_HIGH_AR = 25; + protected const int ATTRIB_ID_READING_SLIDERS = 27; + protected const int ATTRIB_ID_READING_HIDDEN = 29; /// /// The mods which were applied to the beatmap. From cede416d41dcc692bd5011e5b55fe82b251514bf Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 26 Jan 2024 17:22:25 +0200 Subject: [PATCH 36/96] Added full-memory cap Now reading difficulty caps at some value if mechanical difficulty and length is too low --- .../Difficulty/OsuDifficultyCalculator.cs | 27 +++++++++------- .../Difficulty/OsuPerformanceCalculator.cs | 31 +++++++++++++++---- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 96fffc346457..ebd716c3fb6e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -84,12 +84,23 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseReadingSliderPerformance = 0; double baseReadingNonARPerformance = baseReadingHiddenPerformance + baseReadingSliderPerformance; + double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; + double drainRate = beatmap.Difficulty.DrainRate; + int maxCombo = beatmap.GetMaxCombo(); + + int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); + int sliderCount = beatmap.HitObjects.Count(h => h is Slider); + int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner); + + // Limit cognition by full memorisation difficulty + double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SumPower) + Math.Pow(baseSpeedPerformance, SumPower), 1.0 / SumPower); + double cognitionPerformance = Math.Pow(Math.Pow(baseFlashlightARPerformance, SumPower) + Math.Pow(baseReadingNonARPerformance, SumPower), 1.0 / SumPower); + cognitionPerformance = OsuPerformanceCalculator.AdjustCognitionPerformance(mechanicalPerformance, cognitionPerformance, hitCirclesCount + sliderCount); + double basePerformance = Math.Pow( - Math.Pow(baseAimPerformance, SumPower) + - Math.Pow(baseSpeedPerformance, SumPower) + - Math.Pow(baseFlashlightARPerformance, SumPower) + - Math.Pow(baseReadingNonARPerformance, SumPower) + Math.Pow(mechanicalPerformance, SumPower) + + Math.Pow(cognitionPerformance, SumPower) , 1.0 / SumPower ); @@ -97,14 +108,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; - double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; - double drainRate = beatmap.Difficulty.DrainRate; - int maxCombo = beatmap.GetMaxCombo(); - - int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); - int sliderCount = beatmap.HitObjects.Count(h => h is Slider); - int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner); - HitWindows hitWindows = new OsuHitWindows(); hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 71815a0415c3..111725405348 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -65,7 +65,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double aimValue = computeAimValue(score, osuAttributes); double speedValue = computeSpeedValue(score, osuAttributes); - double accuracyValue = computeAccuracyValue(score, osuAttributes); + double mechanicalValue = Math.Pow(Math.Pow(aimValue, SumPower) + Math.Pow(speedValue, SumPower), 1.0 / SumPower); // Cognition @@ -76,13 +76,15 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double readingNonARValue = computeReadingNonARValue(score, osuAttributes); double cognitionValue = Math.Pow(Math.Pow(flashlightARValue, SumPower) + Math.Pow(readingNonARValue, SumPower), 1.0 / SumPower); + cognitionValue = AdjustCognitionPerformance(mechanicalValue, cognitionValue, totalHits); + + double accuracyValue = computeAccuracyValue(score, osuAttributes); double totalValue = Math.Pow( - Math.Pow(aimValue, SumPower) + - Math.Pow(speedValue, SumPower) + - Math.Pow(accuracyValue, SumPower) + - Math.Pow(cognitionValue, SumPower), 1.0 / SumPower + Math.Pow(mechanicalValue, SumPower) + + Math.Pow(cognitionValue, SumPower) + + Math.Pow(accuracyValue, SumPower), 1.0 / SumPower ) * multiplier; return new OsuPerformanceAttributes @@ -365,8 +367,25 @@ private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) return Math.Max(countMiss, comboBasedMissCount); } - 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; + + // Adjusts cognition performance accounting for full-memory + public static double AdjustCognitionPerformance(double mechanicalPerformance, double cognitionPerformance, int hitsCount) + { + // Assuming that less than 25 mechanical pp is not worthy for memory + mechanicalPerformance += 25; + + // Agressive length bonus, pretty bad but makes the work done + mechanicalPerformance *= 1.0 + hitsCount / 400.0; + + // Avoid it being broken on millions of pp, ruins it being continious, but it will never happen on normal circumstances + if (mechanicalPerformance > 10000 || cognitionPerformance > 10000) cognitionPerformance = Math.Min(mechanicalPerformance, cognitionPerformance); + else cognitionPerformance = 100 * softmin(mechanicalPerformance / 100, cognitionPerformance / 100, 100); + + return cognitionPerformance; + } + + 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); } } From 394af044d9b05c39e037541d412753661a653e0a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 26 Jan 2024 22:32:23 +0200 Subject: [PATCH 37/96] Added inpredictability calculation Used mainly in high AR also used in HD --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 100 +++++++++++++++--- .../Preprocessing/OsuDifficultyHitObject.cs | 4 + .../Difficulty/Skills/Reading.cs | 4 +- 3 files changed, 90 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index cd5c8e52edb6..2b5c5ec974ca 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -72,46 +72,114 @@ public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty; - // Console.WriteLine($"Object {currObj.StartTime}, {hiddenDifficulty:0.##} + {noteDensityDifficulty:0.##} + {overlapDifficulty:0.##}"); - return difficulty; } - public static double EvaluateHighARDifficultyOf(DifficultyHitObject current, bool applyFollowLineAdjust = false) + public static double EvaluateHighARDifficultyOf(DifficultyHitObject current, bool applyAdjust = false) { - // follow lines make high AR easier, so apply nerf if object isn't new combo - // double adjustedApproachTime = osuCurrObj.Preempt; - // if (applyFollowLineAdjust) adjustedApproachTime += Math.Max(0, (osuCurrObj.FollowLineTime - 200) / 25); - var currObj = (OsuDifficultyHitObject)current; - return highArCurve(currObj.Preempt); + double result = highArCurve(currObj.Preempt); + + if (applyAdjust) + { + double inpredictability = EvaluateInpredictabilityOf(current); + + // follow lines make high AR easier, so apply nerf if object isn't new combo + inpredictability *= 1 + 0.1 * (800 - currObj.FollowLineTime) / 800; + + result *= 0.85 + 0.75 * inpredictability; + } + + return result; } public static double EvaluateHiddenDifficultyOf(DifficultyHitObject current) { var currObj = (OsuDifficultyHitObject)current; - // Maybe I should just pass in clockrate... - double clockRateEstimate = current.BaseObject.StartTime / currObj.StartTime; - double currVelocity = currObj.LazyJumpDistance / currObj.StrainTime; + double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false); double hdDifficulty = 0; - double timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate; + double timeSpentInvisible = getDurationSpentInvisible(currObj) / currObj.ClockRate; double timeDifficultyFactor = calculateDenstityOf(currObj) / 1000; double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15); hdDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible * timeDifficultyFactor, 1) + - (8 + visibleObjectFactor) * currVelocity; + (8 + visibleObjectFactor) * aimDifficulty; + + hdDifficulty *= 0.95 + 0.15 * EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 return hdDifficulty; } - public static double EvaluatePredictabilityOf(DifficultyHitObject current) + // Returns value from 0 to 1, where 0 is very predictable and 1 is very unpredictable + public static double EvaluateInpredictabilityOf(DifficultyHitObject current) { - return 0; + // make the sum equal to 1 + const double velocity_change_part = 0.3; + const double angle_change_part = 0.6; + const double rhythm_change_part = 0.1; + + if (current.BaseObject is Spinner || current.Index == 0 || current.Previous(0).BaseObject is Spinner) + return 0; + + var osuCurrObj = (OsuDifficultyHitObject)current; + var osuLastObj = (OsuDifficultyHitObject)current.Previous(0); + + double velocityChangeBonus = 0; + + double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; + double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime; + + // https://www.desmos.com/calculator/kqxmqc8pkg + if (currVelocity > 0 || prevVelocity > 0) + { + double velocityChange = Math.Max(0, + Math.Min( + Math.Abs(prevVelocity - currVelocity) - 0.5 * Math.Min(currVelocity, prevVelocity), + Math.Max(((OsuHitObject)osuCurrObj.BaseObject).Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Min(currVelocity, prevVelocity)) + )); // Stealed from xexxar + velocityChangeBonus = velocityChange / Math.Max(currVelocity, prevVelocity); // maxiumum is 0.4 + velocityChangeBonus /= 0.4; + } + + double angleChangeBonus = 0; + + if (osuCurrObj.Angle != null && osuLastObj.Angle != null && currVelocity > 0 && prevVelocity > 0) + { + angleChangeBonus = Math.Pow(Math.Sin((double)((osuCurrObj.Angle - osuLastObj.Angle) / 2)), 2); // Also stealed from xexxar + angleChangeBonus *= Math.Min(currVelocity, prevVelocity) / Math.Max(currVelocity, prevVelocity); // Prevent cheesing + } + + double rhythmChangeBonus = 0; + + if (current.Index > 1) + { + var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1); + + double currDelta = osuCurrObj.StrainTime; + double lastDelta = osuLastObj.StrainTime; + + if (osuLastObj.BaseObject is Slider sliderCurr) + { + currDelta -= sliderCurr.Duration / osuCurrObj.ClockRate; + currDelta = Math.Max(0, currDelta); + } + + if (osuLastLastObj.BaseObject is Slider sliderLast) + { + lastDelta -= sliderLast.Duration / osuLastObj.ClockRate; + lastDelta = Math.Max(0, lastDelta); + } + + rhythmChangeBonus = 1 - Math.Min(currDelta, lastDelta) / Math.Max(currDelta, lastDelta); + } + + double result = velocity_change_part * velocityChangeBonus + angle_change_part * angleChangeBonus + rhythm_change_part * rhythmChangeBonus; + return result; } // Returns a list of objects that are visible on screen at @@ -198,7 +266,7 @@ private static double getTimeNerfFactor(double deltaTime) // https://www.desmos.com/calculator/hbj7swzlth private static double highArCurve(double preempt) { - double value = Math.Pow(4, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms + double value = Math.Pow(3, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms) return value; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 06bae08ab8d6..2ba5c3cc10f7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -99,6 +99,7 @@ public class OsuDifficultyHitObject : DifficultyHitObject private readonly OsuHitObject lastObject; public readonly double Preempt; public readonly double FollowLineTime; + public readonly double ClockRate; public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject? lastLastObject, double clockRate, List objects, int index) : base(hitObject, lastObject, clockRate, objects, index) @@ -106,6 +107,9 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje this.lastLastObject = lastLastObject as OsuHitObject; this.lastObject = (OsuHitObject)lastObject; Preempt = BaseObject.TimePreempt / clockRate; + FollowLineTime = 800 / clockRate; // 800ms is follow line appear time + FollowLineTime *= ((OsuHitObject)hitObject).NewCombo ? 0 : 1; // no follow lines when NC + ClockRate = clockRate; // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. StrainTime = Math.Max(DeltaTime, min_delta_time); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index a60dbb3b9690..2c72a6470882 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -144,7 +144,7 @@ protected override double StrainValueAt(DifficultyHitObject current) currentStrain *= strainDecay(current.DeltaTime); double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true, ((OsuDifficultyHitObject)current).Preempt); - aimDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current); + aimDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current, true); currentStrain += aimDifficulty * skillMultiplier; // currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); @@ -178,7 +178,7 @@ protected override double StrainValueAt(DifficultyHitObject current) currentStrain *= strainDecay(currODHO.StrainTime); double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; - speedDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current); + speedDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current, false); currentStrain += speedDifficulty; currentRhythm = currODHO.RhythmDifficulty; From 58159a54cd89985107ec3411b1992961414ddeb3 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 26 Jan 2024 22:41:12 +0200 Subject: [PATCH 38/96] Update Reading.cs --- osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 2c72a6470882..45b551bfbd6f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -197,7 +197,7 @@ public ReadingHidden(Mod[] mods) } private readonly List difficulties = new List(); - private double skillMultiplier => 2.3; + private double skillMultiplier => 2.1; public override void Process(DifficultyHitObject current) { From 8ba3f2eaf9522607f43ae5f2fc2689eb4e93860e Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 27 Jan 2024 18:08:43 +0200 Subject: [PATCH 39/96] Changed reading cap now it's cognition pp cap = FL + mechanical difficulties --- .../Difficulty/OsuDifficultyCalculator.cs | 5 +- .../Difficulty/OsuPerformanceCalculator.cs | 71 +++++++------------ 2 files changed, 27 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index ebd716c3fb6e..84f05940c896 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -93,9 +93,10 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner); // Limit cognition by full memorisation difficulty - double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SumPower) + Math.Pow(baseSpeedPerformance, SumPower), 1.0 / SumPower); double cognitionPerformance = Math.Pow(Math.Pow(baseFlashlightARPerformance, SumPower) + Math.Pow(baseReadingNonARPerformance, SumPower), 1.0 / SumPower); - cognitionPerformance = OsuPerformanceCalculator.AdjustCognitionPerformance(mechanicalPerformance, cognitionPerformance, hitCirclesCount + sliderCount); + double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SumPower) + Math.Pow(baseSpeedPerformance, SumPower), 1.0 / SumPower); + double potentialFlashlightPerformance = OsuPerformanceCalculator.ComputePerfectFlashlightValue(flashlightRating, hitCirclesCount + sliderCount); + cognitionPerformance = OsuPerformanceCalculator.AdjustCognitionPerformance(cognitionPerformance, mechanicalPerformance, potentialFlashlightPerformance); double basePerformance = Math.Pow( diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 111725405348..7f686fd8dd17 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Xml.Linq; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; @@ -69,14 +70,18 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s // Cognition - double flashlightValue = computeFlashlightValue(score, osuAttributes); + double potentialFlashlightValue = computeFlashlightValue(score, osuAttributes); + + double flashlightValue = potentialFlashlightValue; + if (!score.Mods.Any(h => h is OsuModFlashlight)) + flashlightValue = 0.0; double readingARValue = computeReadingARValue(score, osuAttributes); // Reduce AR reading bonus if FL is present double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, 1.8) + Math.Pow(readingARValue, 1.8), 1.0 / 1.8); double readingNonARValue = computeReadingNonARValue(score, osuAttributes); double cognitionValue = Math.Pow(Math.Pow(flashlightARValue, SumPower) + Math.Pow(readingNonARValue, SumPower), 1.0 / SumPower); - cognitionValue = AdjustCognitionPerformance(mechanicalValue, cognitionValue, totalHits); + cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialFlashlightValue); double accuracyValue = computeAccuracyValue(score, osuAttributes); @@ -112,26 +117,8 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut aimValue *= getComboScalingFactor(attributes); - double approachRateFactor = 0.0; - //if (attributes.ApproachRate > 10.33) - // approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); - // for testing - //else if (attributes.ApproachRate < 8.0) - // approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate); - - if (score.Mods.Any(h => h is OsuModRelax)) - approachRateFactor = 0.0; - - aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. - if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); - // for testing - //else if (score.Mods.Any(h => h is OsuModHidden)) - //{ - // // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - // aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); - //} // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = attributes.SliderCount * 0.15; @@ -167,23 +154,11 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib speedValue *= getComboScalingFactor(attributes); - double approachRateFactor = 0.0; - //if (attributes.ApproachRate > 10.33) - // approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); - - speedValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. - if (score.Mods.Any(m => m is OsuModBlinds)) { // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. speedValue *= 1.12; } - // for testing - //else if (score.Mods.Any(m => m is OsuModHidden)) - //{ - // // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - // speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); - //} // Calculate accuracy assuming the worst case scenario double relevantTotalDiff = totalHits - attributes.SpeedNoteCount; @@ -229,21 +204,16 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; - // For testing - //else if (score.Mods.Any(m => m is OsuModHidden)) - // accuracyValue *= 1.08; - if (score.Mods.Any(m => m is OsuModFlashlight)) - accuracyValue *= 1.02; + // It's stupid so i removed it, it's better just to increase FL coef + //if (score.Mods.Any(m => m is OsuModFlashlight)) + // accuracyValue *= 1.02; return accuracyValue; } private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (!score.Mods.Any(h => h is OsuModFlashlight)) - return 0.0; - double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. @@ -264,6 +234,16 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a return flashlightValue; } + public static double ComputePerfectFlashlightValue(double flashlightDifficulty, int objectsCount) + { + double flashlightValue = Math.Pow(flashlightDifficulty, 2.0) * 25.0; + + flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, objectsCount / 200.0) + + (objectsCount > 200 ? 0.2 * Math.Min(1.0, (objectsCount - 200) / 200.0) : 0.0); + + return flashlightValue; + } + private double computeReadingARValue(ScoreInfo score, OsuDifficultyAttributes attributes) { //double readingARValue = Math.Max(computeReadingLowARValue(score, attributes), computeReadingHighARValue(score, attributes)); @@ -371,17 +351,14 @@ private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) private int totalHits => countGreat + countOk + countMeh + countMiss; // Adjusts cognition performance accounting for full-memory - public static double AdjustCognitionPerformance(double mechanicalPerformance, double cognitionPerformance, int hitsCount) + public static double AdjustCognitionPerformance(double cognitionPerformance, double mechanicalPerformance, double flaslightPerformance) { // Assuming that less than 25 mechanical pp is not worthy for memory - mechanicalPerformance += 25; - - // Agressive length bonus, pretty bad but makes the work done - mechanicalPerformance *= 1.0 + hitsCount / 400.0; + double capPerformance = mechanicalPerformance + flaslightPerformance + 25; // Avoid it being broken on millions of pp, ruins it being continious, but it will never happen on normal circumstances - if (mechanicalPerformance > 10000 || cognitionPerformance > 10000) cognitionPerformance = Math.Min(mechanicalPerformance, cognitionPerformance); - else cognitionPerformance = 100 * softmin(mechanicalPerformance / 100, cognitionPerformance / 100, 100); + if (capPerformance > 10000 || cognitionPerformance > 10000) cognitionPerformance = Math.Min(capPerformance, cognitionPerformance); + else cognitionPerformance = 100 * softmin(capPerformance / 100, cognitionPerformance / 100, 100); return cognitionPerformance; } From 51eb5c0a01585569e2a80abcdd59365b95c2bc99 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 28 Jan 2024 23:51:56 +0200 Subject: [PATCH 40/96] Alternating angle nerf Fixed alternating angle (fiery jumps for example) gaining too much unfair reading pp Reworked similar-angle nerf as a whole Normalised global pp multiplier --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 88 ++++++++++++++++--- .../Difficulty/OsuDifficultyCalculator.cs | 5 +- .../Difficulty/OsuPerformanceCalculator.cs | 22 ++--- .../Difficulty/Skills/Reading.cs | 23 +++-- 4 files changed, 107 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 2b5c5ec974ca..7c6c82fdb98f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -17,7 +17,7 @@ public static class ReadingEvaluator private const double overlap_multiplier = 0.8; - private static double calculateDenstityOf(OsuDifficultyHitObject currObj) + public static double CalculateDenstityOf(OsuDifficultyHitObject currObj) { double pastObjectDifficultyInfluence = 0; @@ -38,7 +38,7 @@ private static double calculateDenstityOf(OsuDifficultyHitObject currObj) return pastObjectDifficultyInfluence; } - private static double calculateOverlapDifficultyOf(OsuDifficultyHitObject currObj) + public static double CalculateOverlapDifficultyOf(OsuDifficultyHitObject currObj) { double screenOverlapDifficulty = 0; @@ -62,15 +62,19 @@ public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) var currObj = (OsuDifficultyHitObject)current; - double pastObjectDifficultyInfluence = calculateDenstityOf(currObj); - double screenOverlapDifficulty = calculateOverlapDifficultyOf(currObj); + double pastObjectDifficultyInfluence = CalculateDenstityOf(currObj); + double screenOverlapDifficulty = CalculateOverlapDifficultyOf(currObj); double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence)), 2.3); screenOverlapDifficulty = Math.Max(0, screenOverlapDifficulty - 0.5); // make overlap value =1 cost significantly less + + double overlapBonus = overlap_multiplier * screenOverlapDifficulty * difficulty; + difficulty *= getConstantAngleNerfFactor(currObj); + difficulty += overlapBonus; - difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty; + //difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty; return difficulty; } @@ -103,12 +107,16 @@ public static double EvaluateHiddenDifficultyOf(DifficultyHitObject current) double hdDifficulty = 0; double timeSpentInvisible = getDurationSpentInvisible(currObj) / currObj.ClockRate; - double timeDifficultyFactor = calculateDenstityOf(currObj) / 1000; + + double density = 1 + Math.Max(0, CalculateDenstityOf(currObj) - 1); + density *= getConstantAngleNerfFactor(currObj); + + double timeDifficultyFactor = density / 1000; double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15); hdDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible * timeDifficultyFactor, 1) + - (8 + visibleObjectFactor) * aimDifficulty; + (6 + visibleObjectFactor) * aimDifficulty; hdDifficulty *= 0.95 + 0.15 * EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 @@ -175,13 +183,28 @@ public static double EvaluateInpredictabilityOf(DifficultyHitObject current) lastDelta = Math.Max(0, lastDelta); } - rhythmChangeBonus = 1 - Math.Min(currDelta, lastDelta) / Math.Max(currDelta, lastDelta); + rhythmChangeBonus = getRhythmDifference(currDelta, lastDelta); } double result = velocity_change_part * velocityChangeBonus + angle_change_part * angleChangeBonus + rhythm_change_part * rhythmChangeBonus; return result; } + public static double EvaluateLowDensityBonusOf(DifficultyHitObject current) + { + //var currObj = (OsuDifficultyHitObject)current; + + //// Density = 2 in general means 3 notes on screen (it's not including current note) + //double density = CalculateDenstityOf(currObj); + + //// We are considering density = 1.5 as starting point, 1.0 is noticably uncomfy and 0.5 is severely uncomfy + //double bonus = 1.5 - density; + //if (bonus <= 0) return 0; + + //return Math.Pow(bonus, 2); + return 0; + } + // Returns a list of objects that are visible on screen at // the point in time at which the current object becomes visible. private static IEnumerable retrievePastVisibleObjects(OsuDifficultyHitObject current) @@ -237,6 +260,13 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) int index = 0; double currentTimeGap = 0; + OsuDifficultyHitObject prevLoopObj = current; + + OsuDifficultyHitObject? prevLoopObj1 = null; + OsuDifficultyHitObject? prevLoopObj2 = null; + + double prevConstantAngle = 0; + while (currentTimeGap < time_limit) { var loopObj = (OsuDifficultyHitObject)current.Previous(index); @@ -246,18 +276,52 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - time_limit_low) / (time_limit - time_limit_low), 0, 1); - if (loopObj.Angle.IsNotNull() && current.Angle.IsNotNull()) + if (loopObj.Angle.IsNotNull() && prevLoopObj.Angle.IsNotNull()) { - double angleDifference = Math.Abs(current.Angle.Value - loopObj.Angle.Value); - constantAngleCount += Math.Cos(4 * Math.Min(Math.PI / 8, angleDifference)) * longIntervalFactor; + double angleDifference = Math.Abs(prevLoopObj.Angle.Value - loopObj.Angle.Value); + + // Nerf alternating angles case + if (prevLoopObj1.IsNotNull() && prevLoopObj2.IsNotNull() && prevLoopObj1.Angle.IsNotNull() && prevLoopObj2.Angle.IsNotNull()) + { + // Normalized difference + double angleDifference1 = Math.Abs(prevLoopObj1.Angle.Value - loopObj.Angle.Value) / Math.PI; + double angleDifference2 = Math.Abs(prevLoopObj2.Angle.Value - prevLoopObj.Angle.Value) / Math.PI; + + // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 + double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); + + // Be sure to nerf only same rhythms + double rhythmFactor = 1 - getRhythmDifference(loopObj.StrainTime, prevLoopObj.StrainTime); // 0 on different rhythm, 1 on same rhythm + rhythmFactor *= 1 - getRhythmDifference(prevLoopObj.StrainTime, prevLoopObj1.StrainTime); + rhythmFactor *= 1 - getRhythmDifference(prevLoopObj1.StrainTime, prevLoopObj2.StrainTime); + + double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevLoopObj.Angle.Value) / Math.PI; + + double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); + + prevAngleAdjust *= alternatingFactor; // Nerf if alternating + prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms + prevAngleAdjust *= acuteAngleFactor; + + angleDifference -= prevAngleAdjust; + } + + double currConstantAngle = Math.Cos(4 * Math.Min(Math.PI / 8, angleDifference)) * longIntervalFactor; + constantAngleCount += Math.Min(currConstantAngle, prevConstantAngle); + prevConstantAngle = currConstantAngle; } currentTimeGap = current.StartTime - loopObj.StartTime; index++; + + prevLoopObj2 = prevLoopObj1; + prevLoopObj1 = prevLoopObj; + prevLoopObj = loopObj; } return Math.Pow(Math.Min(1, 2 / constantAngleCount), 2); } + private static double getTimeNerfFactor(double deltaTime) { return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); @@ -270,6 +334,8 @@ private static double highArCurve(double preempt) value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms) return value; } + + private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2); private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); // We are using mutiply and divide instead of add and subtract, so values won't be negative diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 84f05940c896..caf9588b945e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -22,10 +22,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyCalculator : DifficultyCalculator { - private const double difficulty_multiplier = 0.0675; + private const double difficulty_multiplier = 0.067; public override int Version => 20220902; public static double SumPower => 1.1; + public static double FLSumPower => 1.6; public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -75,7 +76,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseReadingHighARPerformance = Math.Pow(5 * Math.Max(1, readingHighARRating / 0.0675) - 4, 3) / 100000; double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SumPower) + Math.Pow(baseReadingHighARPerformance, SumPower), 1.0 / SumPower); - double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, 1.8) + Math.Pow(baseReadingARPerformance, 1.8), 1.0 / 1.8); + double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FLSumPower) + Math.Pow(baseReadingARPerformance, FLSumPower), 1.0 / FLSumPower); double baseReadingHiddenPerformance = 0; if (mods.Any(h => h is OsuModHidden)) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 7f686fd8dd17..979bda168f4a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -16,8 +16,6 @@ public class OsuPerformanceCalculator : PerformanceCalculator { public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. - public static double SumPower => 1.1; // Maybe it should just use OsuDifficultyCalculator SumPower - private double accuracy; private int scoreMaxCombo; private int countGreat; @@ -64,9 +62,11 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits); } + double power = OsuDifficultyCalculator.SumPower; + double aimValue = computeAimValue(score, osuAttributes); double speedValue = computeSpeedValue(score, osuAttributes); - double mechanicalValue = Math.Pow(Math.Pow(aimValue, SumPower) + Math.Pow(speedValue, SumPower), 1.0 / SumPower); + double mechanicalValue = Math.Pow(Math.Pow(aimValue, power) + Math.Pow(speedValue, power), 1.0 / power); // Cognition @@ -77,19 +77,20 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s flashlightValue = 0.0; double readingARValue = computeReadingARValue(score, osuAttributes); // Reduce AR reading bonus if FL is present - double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, 1.8) + Math.Pow(readingARValue, 1.8), 1.0 / 1.8); + double flPower = OsuDifficultyCalculator.FLSumPower; + double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, flPower) + Math.Pow(readingARValue, flPower), 1.0 / flPower); double readingNonARValue = computeReadingNonARValue(score, osuAttributes); - double cognitionValue = Math.Pow(Math.Pow(flashlightARValue, SumPower) + Math.Pow(readingNonARValue, SumPower), 1.0 / SumPower); + double cognitionValue = Math.Pow(Math.Pow(flashlightARValue, power) + Math.Pow(readingNonARValue, power), 1.0 / power); cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialFlashlightValue); double accuracyValue = computeAccuracyValue(score, osuAttributes); double totalValue = Math.Pow( - Math.Pow(mechanicalValue, SumPower) + - Math.Pow(cognitionValue, SumPower) + - Math.Pow(accuracyValue, SumPower), 1.0 / SumPower + Math.Pow(mechanicalValue, power) + + Math.Pow(cognitionValue, power) + + Math.Pow(accuracyValue, power), 1.0 / power ) * multiplier; return new OsuPerformanceAttributes @@ -247,9 +248,10 @@ public static double ComputePerfectFlashlightValue(double flashlightDifficulty, private double computeReadingARValue(ScoreInfo score, OsuDifficultyAttributes attributes) { //double readingARValue = Math.Max(computeReadingLowARValue(score, attributes), computeReadingHighARValue(score, attributes)); + double power = OsuDifficultyCalculator.SumPower; double readingValue = Math.Pow( - Math.Pow(computeReadingLowARValue(score, attributes), SumPower) + - Math.Pow(computeReadingHighARValue(score, attributes), SumPower), 1.0 / SumPower); + Math.Pow(computeReadingLowARValue(score, attributes), power) + + Math.Pow(computeReadingHighARValue(score, attributes), power), 1.0 / power); return readingValue; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 45b551bfbd6f..13fc1ef8bb0c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - //private double skillMultiplier => 5.5; - private double skillMultiplier => 2.3; + //private double skillMultiplier => 2.3; + private double skillMultiplier => 2; public ReadingLowAR(Mod[] mods) : base(mods) @@ -132,7 +132,8 @@ public HighARAimComponent(Mod[] mods) private double currentStrain; // private double currentRhythm; - private double skillMultiplier => 13; + //private double skillMultiplier => 13; + private double skillMultiplier => 14; private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); @@ -145,18 +146,24 @@ protected override double StrainValueAt(DifficultyHitObject current) double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true, ((OsuDifficultyHitObject)current).Preempt); aimDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current, true); - currentStrain += aimDifficulty * skillMultiplier; - - // currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); + aimDifficulty *= skillMultiplier; double totalStrain = currentStrain; + currentStrain += aimDifficulty; + + // Warning: this line is unstable, so increasing amount of objects can decrease pp + totalStrain += aimDifficulty * (1 + ReadingEvaluator.EvaluateLowDensityBonusOf(current)); + + + //Console.WriteLine($"{current.StartTime} - {ReadingEvaluator.EvaluateLowDensityBonusOf(current)}"); + return totalStrain; } } public class HighARSpeedComponent : OsuStrainSkill { - private double skillMultiplier => 650; + private double skillMultiplier => 675; private double strainDecayBase => 0.3; private double currentStrain; @@ -197,7 +204,7 @@ public ReadingHidden(Mod[] mods) } private readonly List difficulties = new List(); - private double skillMultiplier => 2.1; + private double skillMultiplier => 2.3; public override void Process(DifficultyHitObject current) { From ba265ac2d9f33acaa57a51b6e2408a2a42758983 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 29 Jan 2024 00:20:58 +0200 Subject: [PATCH 41/96] Update LegacyScoreDecoder.cs Just to make osu-tools work --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index e51a95798b3b..65e2c026550b 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -180,7 +180,7 @@ private void readCompressedData(byte[] data, Action readFunc) /// /// The score to populate the statistics of. /// The corresponding . - internal static void PopulateMaximumStatistics(ScoreInfo score, WorkingBeatmap workingBeatmap) + public static void PopulateMaximumStatistics(ScoreInfo score, WorkingBeatmap workingBeatmap) { Debug.Assert(score.BeatmapInfo != null); From 1a68e29a957d7efe50c89c635de186bdce19eb67 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 2 Feb 2024 21:15:05 +0200 Subject: [PATCH 42/96] Big amount of changes 1) Fully remade HD calc: now it's strain-based 2) Remade high AR calc: now it's using more correct aim-speed summing 3) Added explicit nerf for fiery patterns 4) Fixed bug where HR pop-offing slideraim difficulty due to sliderend position not being mirrored (no longer Rat Race +50) 5) Splitted some files cuz it's more convenient for me to edit --- .../Difficulty/Evaluators/AimEvaluator.cs | 13 +- .../Difficulty/Evaluators/ReadingEvaluator.cs | 144 +++--------- .../Evaluators/ReadingHiddenEvaluator.cs | 219 ++++++++++++++++++ .../Evaluators/ReadingHighAREvaluator.cs | 149 ++++++++++++ .../Difficulty/OsuDifficultyCalculator.cs | 46 ++-- .../Difficulty/OsuPerformanceCalculator.cs | 93 +++++--- .../Preprocessing/OsuDifficultyHitObject.cs | 18 +- .../Difficulty/Skills/OsuStrainSkill.cs | 11 + .../Difficulty/Skills/Reading.cs | 160 +------------ .../Difficulty/Skills/ReadingHighAR.cs | 146 ++++++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 +- 11 files changed, 670 insertions(+), 333 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHiddenEvaluator.cs create mode 100644 osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs create mode 100644 osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs index 118468cce6ef..3d1939acac5f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs @@ -24,7 +24,7 @@ public static class AimEvaluator /// and slider difficulty. /// /// - public static double EvaluateDifficultyOf(DifficultyHitObject current, bool withSliderTravelDistance, double clampPreemptTime = 0) + public static double EvaluateDifficultyOf(DifficultyHitObject current, bool withSliderTravelDistance) { if (current.BaseObject is Spinner || current.Index <= 1 || current.Previous(0).BaseObject is Spinner) return 0; @@ -121,17 +121,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with // Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger. aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier); - if (clampPreemptTime > 0) - { - // Scale if AR is too high for high AR calc - double multiplier = osuCurrObj.StrainTime / Math.Min(osuCurrObj.StrainTime, clampPreemptTime - 150); // 150ms is considered as reaction time - double multiplierIfAR11 = osuCurrObj.StrainTime / Math.Min(osuCurrObj.StrainTime, 150); - - multiplier = Math.Min(multiplier, multiplierIfAR11); - - aimStrain *= multiplier; - } - // Add in additional slider velocity bonus. if (withSliderTravelDistance) aimStrain += sliderBonus * slider_multiplier; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 7c6c82fdb98f..90fb1758d0bc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { + // Main class with some util functions public static class ReadingEvaluator { private const double reading_window_size = 3000; @@ -26,7 +27,7 @@ public static double CalculateDenstityOf(OsuDifficultyHitObject currObj) double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 90) / 15); + loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 60) / 10); //double timeBetweenCurrAndLoopObj = (currObj.BaseObject.StartTime - loopObj.BaseObject.StartTime) / clockRateEstimate; double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime; @@ -79,57 +80,13 @@ public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) return difficulty; } - public static double EvaluateHighARDifficultyOf(DifficultyHitObject current, bool applyAdjust = false) - { - var currObj = (OsuDifficultyHitObject)current; - - double result = highArCurve(currObj.Preempt); - - if (applyAdjust) - { - double inpredictability = EvaluateInpredictabilityOf(current); - - // follow lines make high AR easier, so apply nerf if object isn't new combo - inpredictability *= 1 + 0.1 * (800 - currObj.FollowLineTime) / 800; - - result *= 0.85 + 0.75 * inpredictability; - } - - return result; - } - - public static double EvaluateHiddenDifficultyOf(DifficultyHitObject current) - { - var currObj = (OsuDifficultyHitObject)current; - - double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false); - - double hdDifficulty = 0; - - double timeSpentInvisible = getDurationSpentInvisible(currObj) / currObj.ClockRate; - - double density = 1 + Math.Max(0, CalculateDenstityOf(currObj) - 1); - density *= getConstantAngleNerfFactor(currObj); - - double timeDifficultyFactor = density / 1000; - - double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15); - - hdDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible * timeDifficultyFactor, 1) + - (6 + visibleObjectFactor) * aimDifficulty; - - hdDifficulty *= 0.95 + 0.15 * EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 - - return hdDifficulty; - } - // Returns value from 0 to 1, where 0 is very predictable and 1 is very unpredictable public static double EvaluateInpredictabilityOf(DifficultyHitObject current) { // make the sum equal to 1 - const double velocity_change_part = 0.3; - const double angle_change_part = 0.6; - const double rhythm_change_part = 0.1; + const double velocity_change_part = 0.25; + const double angle_change_part = 0.45; + const double rhythm_change_part = 0.3; if (current.BaseObject is Spinner || current.Index == 0 || current.Previous(0).BaseObject is Spinner) return 0; @@ -137,23 +94,19 @@ public static double EvaluateInpredictabilityOf(DifficultyHitObject current) var osuCurrObj = (OsuDifficultyHitObject)current; var osuLastObj = (OsuDifficultyHitObject)current.Previous(0); - double velocityChangeBonus = 0; + // Rhythm difference punishment for velocity and angle bonuses + double rhythmSimilarity = 1 - getRhythmDifference(osuCurrObj.StrainTime, osuLastObj.StrainTime); + + // Make differentiation going from 1/4 to 1/2 and bigger difference + // To 1/3 to 1/2 and smaller difference + rhythmSimilarity = Math.Clamp(rhythmSimilarity, 0.5, 0.75); + rhythmSimilarity = 4 * (rhythmSimilarity - 0.5); + + double velocityChangeBonus = getVelocityChangeFactor(osuCurrObj, osuLastObj) * rhythmSimilarity; double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime; - // https://www.desmos.com/calculator/kqxmqc8pkg - if (currVelocity > 0 || prevVelocity > 0) - { - double velocityChange = Math.Max(0, - Math.Min( - Math.Abs(prevVelocity - currVelocity) - 0.5 * Math.Min(currVelocity, prevVelocity), - Math.Max(((OsuHitObject)osuCurrObj.BaseObject).Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Min(currVelocity, prevVelocity)) - )); // Stealed from xexxar - velocityChangeBonus = velocityChange / Math.Max(currVelocity, prevVelocity); // maxiumum is 0.4 - velocityChangeBonus /= 0.4; - } - double angleChangeBonus = 0; if (osuCurrObj.Angle != null && osuLastObj.Angle != null && currVelocity > 0 && prevVelocity > 0) @@ -162,6 +115,9 @@ public static double EvaluateInpredictabilityOf(DifficultyHitObject current) angleChangeBonus *= Math.Min(currVelocity, prevVelocity) / Math.Max(currVelocity, prevVelocity); // Prevent cheesing } + angleChangeBonus *= rhythmSimilarity; + + // This bonus only awards rhythm changes if they're not filled with sliderends double rhythmChangeBonus = 0; if (current.Index > 1) @@ -190,19 +146,26 @@ public static double EvaluateInpredictabilityOf(DifficultyHitObject current) return result; } - public static double EvaluateLowDensityBonusOf(DifficultyHitObject current) + private static double getVelocityChangeFactor(OsuDifficultyHitObject osuCurrObj, OsuDifficultyHitObject osuLastObj) { - //var currObj = (OsuDifficultyHitObject)current; + double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; + double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime; - //// Density = 2 in general means 3 notes on screen (it's not including current note) - //double density = CalculateDenstityOf(currObj); + double velocityChangeFactor = 0; - //// We are considering density = 1.5 as starting point, 1.0 is noticably uncomfy and 0.5 is severely uncomfy - //double bonus = 1.5 - density; - //if (bonus <= 0) return 0; + // https://www.desmos.com/calculator/kqxmqc8pkg + if (currVelocity > 0 || prevVelocity > 0) + { + double velocityChange = Math.Max(0, + Math.Min( + Math.Abs(prevVelocity - currVelocity) - 0.5 * Math.Min(currVelocity, prevVelocity), + Math.Max(((OsuHitObject)osuCurrObj.BaseObject).Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Min(currVelocity, prevVelocity)) + )); // Stealed from xexxar + velocityChangeFactor = velocityChange / Math.Max(currVelocity, prevVelocity); // maxiumum is 0.4 + velocityChangeFactor /= 0.4; + } - //return Math.Pow(bonus, 2); - return 0; + return velocityChangeFactor; } // Returns a list of objects that are visible on screen at @@ -222,35 +185,6 @@ private static IEnumerable retrievePastVisibleObjects(Os } } - private static List retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) - { - List objects = new List(); - - for (int i = 0; i < current.Count; i++) - { - OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Next(i); - - if (hitObject.IsNull() || - (hitObject.StartTime - current.StartTime) > reading_window_size || - current.StartTime < hitObject.StartTime - hitObject.Preempt) - break; - - objects.Add(hitObject); - } - - return objects; - } - - private static double getDurationSpentInvisible(OsuDifficultyHitObject current) - { - var baseObject = (OsuHitObject)current.BaseObject; - - double fadeOutStartTime = baseObject.StartTime - baseObject.TimePreempt + baseObject.TimeFadeIn; - double fadeOutDuration = baseObject.TimePreempt * OsuModHidden.FADE_OUT_DURATION_MULTIPLIER; - - return (fadeOutStartTime + fadeOutDuration) - (baseObject.StartTime - baseObject.TimePreempt); - } - private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) { const double time_limit = 2000; @@ -327,19 +261,7 @@ private static double getTimeNerfFactor(double deltaTime) return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); } - // https://www.desmos.com/calculator/hbj7swzlth - private static double highArCurve(double preempt) - { - double value = Math.Pow(3, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms - value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms) - return value; - } - private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2); private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); - - // We are using mutiply and divide instead of add and subtract, so values won't be negative - // https://www.desmos.com/calculator/fv5xerwpd2 - 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); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHiddenEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHiddenEvaluator.cs new file mode 100644 index 000000000000..6b25e4657ae5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHiddenEvaluator.cs @@ -0,0 +1,219 @@ +// 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.Framework.Extensions.ObjectExtensions; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; + +namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators +{ + // Class for HD calc. Split because there are a lot of things in HD calc. + public static class ReadingHiddenEvaluator + { + private const double reading_window_size = 3000; + + public static double EvaluateDifficultyOf(DifficultyHitObject current) + { + var currObj = (OsuDifficultyHitObject)current; + + double density = 0; + double densityAnglesNerf = -2; // we have threshold of 2, so 2 or same angles won't be punished + + OsuDifficultyHitObject? prevObj0 = null; + OsuDifficultyHitObject? prevObj1 = null; + OsuDifficultyHitObject? prevObj2 = null; + + double prevConstantAngle = 0; + + foreach (var loopObj in retrievePastVisibleObjects(currObj).Reverse()) + { + double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); + + // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. + // For HD: it's not subtracting anything cuz it's multiplied by the aim difficulty anyways. + // loopDifficulty *= logistic((loopObj.MinimumJumpDistance) / 15); + + // Reduce density bonus for this object if they're too apart in time + // Nerf starts on 1500ms and reaches maximum (*=0) on 3000ms + double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime; + loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); + + if (prevObj0.IsNull()) + { + prevObj0 = loopObj; + continue; + } + + // HD-exclusive burst nerf + + // Only if next object is slower, representing break from many notes in a row + if (loopObj.StrainTime > prevObj0.StrainTime) + { + // Get rhythm similarity: 1 on same rhythms, 0.5 on 1/4 to 1/2 + double rhythmSimilarity = 1 - getRhythmDifference(loopObj.StrainTime, prevObj0.StrainTime); + + // Make differentiation going from 1/4 to 1/2 and bigger difference + // To 1/3 to 1/2 and smaller difference + rhythmSimilarity = Math.Clamp(rhythmSimilarity, 0.5, 0.75); + rhythmSimilarity = 4 * (rhythmSimilarity - 0.5); + + // Reduce density for this objects if rhythms are different + loopDifficulty *= rhythmSimilarity; + } + + density += loopDifficulty; + + // Angles nerf + + if (loopObj.Angle.IsNotNull() && prevObj0.Angle.IsNotNull()) + { + double angleDifference = Math.Abs(prevObj0.Angle.Value - loopObj.Angle.Value); + + // Nerf alternating angles case + if (prevObj1.IsNotNull() && prevObj2.IsNotNull() && prevObj1.Angle.IsNotNull() && prevObj2.Angle.IsNotNull()) + { + // Normalized difference + double angleDifference1 = Math.Abs(prevObj1.Angle.Value - loopObj.Angle.Value) / Math.PI; + double angleDifference2 = Math.Abs(prevObj2.Angle.Value - prevObj0.Angle.Value) / Math.PI; + + // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 + double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); + + // Be sure to nerf only same rhythms + double rhythmFactor = 1 - getRhythmDifference(loopObj.StrainTime, prevObj0.StrainTime); // 0 on different rhythm, 1 on same rhythm + rhythmFactor *= 1 - getRhythmDifference(prevObj0.StrainTime, prevObj1.StrainTime); + rhythmFactor *= 1 - getRhythmDifference(prevObj1.StrainTime, prevObj2.StrainTime); + + double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevObj0.Angle.Value) / Math.PI; + + double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); + + prevAngleAdjust *= alternatingFactor; // Nerf if alternating + prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms + prevAngleAdjust *= acuteAngleFactor; + + angleDifference -= prevAngleAdjust; + } + + // Reduce angles nerf if objects are too apart in time + // Angle nerf is starting being reduced from 200ms (150BPM jump) and it reduced to 0 on 2000ms + double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - 200) / (2000 - 200), 0, 1); + + // Current angle nerf. Angle difference less than 15 degrees is considered the same + double currConstantAngle = Math.Cos(4 * Math.Min(Math.PI / 12, angleDifference)) * longIntervalFactor; + + // Apply the nerf only when it's repeated + double currentAngleNerf = Math.Min(currConstantAngle, prevConstantAngle); + + densityAnglesNerf += Math.Min(currentAngleNerf, loopDifficulty); + prevConstantAngle = currConstantAngle; + } + + prevObj2 = prevObj1; + prevObj1 = prevObj0; + prevObj0 = loopObj; + } + + // Apply angles nerf + density -= Math.Max(0, densityAnglesNerf); + + // Consider that density matters only starting from 3rd note on the screen + double densityFactor = Math.Max(0, density - 1) / 4; + + // This is kinda wrong cuz it returns value bigger than preempt + // double timeSpentInvisible = getDurationSpentInvisible(currObj) / 1000 / currObj.ClockRate; + + // The closer timeSpentInvisible is to 0 -> the less difference there are between NM and HD + // So we will reduce base according to this + // It will be 0.354 on AR11 value + double invisibilityFactor = logistic(currObj.Preempt / 120 - 4); + + double hdDifficulty = invisibilityFactor + densityFactor; + + // Scale by inpredictability slightly + hdDifficulty *= 0.95 + 0.15 * ReadingEvaluator.EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 + + return hdDifficulty; + } + + //public static double EvaluateHiddenDifficultyOfOld(DifficultyHitObject current) + //{ + // var currObj = (OsuDifficultyHitObject)current; + + // double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false); + + // double timeSpentInvisible = getDurationSpentInvisible(currObj) / currObj.ClockRate; + + // double density = 1 + Math.Max(0, CalculateDenstityOf(currObj) - 1); + + // double timeDifficultyFactor = density / 1000; + // timeDifficultyFactor *= getConstantAngleNerfFactor(currObj); + + // double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15); + + // double hdDifficulty = visibleObjectFactor * timeSpentInvisible * timeDifficultyFactor + + // (6 + visibleObjectFactor) * aimDifficulty; + + // hdDifficulty *= 0.95 + 0.15 * EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 + + // return hdDifficulty; + //} + + // Returns a list of objects that are visible on screen at + // the point in time at which the current object becomes visible. + private static IEnumerable retrievePastVisibleObjects(OsuDifficultyHitObject current) + { + for (int i = 0; i < current.Index; i++) + { + OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i); + + if (hitObject.IsNull() || + current.StartTime - hitObject.StartTime > reading_window_size || + hitObject.StartTime < current.StartTime - current.Preempt) + break; + + yield return hitObject; + } + } + + //private static double getDurationSpentInvisible(OsuDifficultyHitObject current) + //{ + // var baseObject = (OsuHitObject)current.BaseObject; + + // double fadeOutStartTime = baseObject.StartTime - baseObject.TimePreempt + baseObject.TimeFadeIn; + // double fadeOutDuration = baseObject.TimePreempt * OsuModHidden.FADE_OUT_DURATION_MULTIPLIER; + + // return (fadeOutStartTime + fadeOutDuration) - (baseObject.StartTime - baseObject.TimePreempt); + //} + + //private static List retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) + //{ + // List objects = new List(); + + // for (int i = 0; i < current.Count; i++) + // { + // OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Next(i); + + // if (hitObject.IsNull() || + // (hitObject.StartTime - current.StartTime) > reading_window_size || + // current.StartTime < hitObject.StartTime - hitObject.Preempt) + // break; + + // objects.Add(hitObject); + // } + + // return objects; + //} + + private static double getTimeNerfFactor(double deltaTime) + { + return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); + } + + private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2); + private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs new file mode 100644 index 000000000000..aea7f15ab292 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs @@ -0,0 +1,149 @@ +// 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 osu.Framework.Extensions.ObjectExtensions; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators +{ + // Main class with some util functions + public static class ReadingHighAREvaluator + { + public static double EvaluateDifficultyOf(DifficultyHitObject current, bool applyAdjust = false) + { + var currObj = (OsuDifficultyHitObject)current; + + double result = highArCurve(currObj.Preempt); + + if (applyAdjust) + { + double inpredictability = ReadingEvaluator.EvaluateInpredictabilityOf(current); + + // follow lines make high AR easier, so apply nerf if object isn't new combo + inpredictability *= 1 + 0.1 * (800 - currObj.FollowLineTime) / 800; + + result *= 0.85 + 1 * inpredictability; + result *= 1.05 - 0.4 * EvaluateFieryAnglePunishmentOf(current); + } + + return result; + } + + // Explicitely nerfs edgecased fiery-type jumps for high AR. The difference from Inpredictability is that this is not used in HD calc + public static double EvaluateFieryAnglePunishmentOf(DifficultyHitObject current) + { + if (current.Index <= 2) + return 0; + + var currObj = (OsuDifficultyHitObject)current; + var lastObj0 = (OsuDifficultyHitObject)current.Previous(0); + var lastObj1 = (OsuDifficultyHitObject)current.Previous(1); + var lastObj2 = (OsuDifficultyHitObject)current.Previous(2); + + if (currObj.Angle.IsNull() || lastObj0.Angle.IsNull() || lastObj1.Angle.IsNull() || lastObj2.Angle.IsNull()) + return 0; + + // Punishment will be reduced if velocity is changing + double velocityChangeFactor = getVelocityChangeFactor(currObj, lastObj0); + velocityChangeFactor = 1 - Math.Pow(velocityChangeFactor, 2); + + double a1 = currObj.Angle.Value / Math.PI; + double a2 = lastObj0.Angle.Value / Math.PI; + double a3 = lastObj1.Angle.Value / Math.PI; + double a4 = lastObj2.Angle.Value / Math.PI; + + // - 4 same sharp angles in a row: (0.3 0.3 0.3 0.3) -> max punishment + + // Normalized difference + double angleDifference1 = Math.Abs(a1 - a2); + double angleDifference2 = Math.Abs(a1 - a3); + double angleDifference3 = Math.Abs(a1 - a4); + + // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 + double sameAnglePunishment = Math.Pow((1 - angleDifference1) * (1 - angleDifference2) * (1 - angleDifference3), 3); + + // Starting from 60 degrees - reduce same angle punishment + double angleSharpnessFactor = Math.Max(0, a1 - 1.0 / 3); + angleSharpnessFactor = 1 - angleSharpnessFactor; + + sameAnglePunishment *= angleSharpnessFactor; + sameAnglePunishment *= velocityChangeFactor; + sameAnglePunishment *= 0.75; + + // - Alternating angles with 0: (0.3 0 0.3 0) or (0 0.3 0 0.3) -> max punishment, (0.3 0 0.1 0) -> some punishment + + double alternateWithZeroAnglePunishment = Math.Max( + getAlternateWithZeroAnglePunishment(a1, a2, a3, a4), + getAlternateWithZeroAnglePunishment(a2, a1, a4, a3)); + alternateWithZeroAnglePunishment *= velocityChangeFactor; + + return Math.Min(1, sameAnglePunishment + alternateWithZeroAnglePunishment); + } + + private static double getVelocityChangeFactor(OsuDifficultyHitObject osuCurrObj, OsuDifficultyHitObject osuLastObj) + { + double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; + double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime; + + double velocityChangeFactor = 0; + + // https://www.desmos.com/calculator/kqxmqc8pkg + if (currVelocity > 0 || prevVelocity > 0) + { + double velocityChange = Math.Max(0, + Math.Min( + Math.Abs(prevVelocity - currVelocity) - 0.5 * Math.Min(currVelocity, prevVelocity), + Math.Max(((OsuHitObject)osuCurrObj.BaseObject).Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Min(currVelocity, prevVelocity)) + )); // Stealed from xexxar + velocityChangeFactor = velocityChange / Math.Max(currVelocity, prevVelocity); // maxiumum is 0.4 + velocityChangeFactor /= 0.4; + } + + return velocityChangeFactor; + } + + private static double getAlternateWithZeroAnglePunishment(double a1, double a2, double a3, double a4) + { + // We assume that a1 and a3 are 0 + double zeroFactor = Math.Pow((1 - a1) * (1 - a3), 8); + zeroFactor *= Math.Pow(1 - Math.Abs(a1 - a3), 2); + + double angleSimilarityFactor = 1 - Math.Abs(a2 - a4); + double angleSharpnessFactor = Math.Min(1 - Math.Max(0, a2 - 1.0 / 3), 1 - Math.Max(0, a4 - 1.0 / 3)); + + return zeroFactor * angleSimilarityFactor * angleSharpnessFactor; + } + + public static double EvaluateLowDensityBonusOf(DifficultyHitObject current) + { + //var currObj = (OsuDifficultyHitObject)current; + + //// Density = 2 in general means 3 notes on screen (it's not including current note) + //double density = CalculateDenstityOf(currObj); + + //// We are considering density = 1.5 as starting point, 1.0 is noticably uncomfy and 0.5 is severely uncomfy + //double bonus = 1.5 - density; + //if (bonus <= 0) return 0; + + //return Math.Pow(bonus, 2); + return 0; + } + + // https://www.desmos.com/calculator/hbj7swzlth + private static double highArCurve(double preempt) + { + double value = Math.Pow(3, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms + value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms) + return value; + } + + // We are using mutiply and divide instead of add and subtract, so values won't be negative + // https://www.desmos.com/calculator/fv5xerwpd2 + 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); + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index caf9588b945e..364adc58a905 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -22,11 +22,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyCalculator : DifficultyCalculator { - private const double difficulty_multiplier = 0.067; - + public const double DIFFICULTY_MULTIPLIER = 0.067; + public const double SUM_POWER = 1.1; + public const double FL_SUM_POWER = 1.6; public override int Version => 20220902; - public static double SumPower => 1.1; - public static double FLSumPower => 1.6; public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -38,16 +37,16 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat if (beatmap.HitObjects.Count == 0) return new OsuDifficultyAttributes { Mods = mods }; - double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; - double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * DIFFICULTY_MULTIPLIER; + double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * DIFFICULTY_MULTIPLIER; + double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double speedNotes = ((Speed)skills[2]).RelevantNoteCount(); - double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; + double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * DIFFICULTY_MULTIPLIER; - double readingLowARRating = Math.Sqrt(skills[4].DifficultyValue()) * difficulty_multiplier; - double readingHighARRating = Math.Sqrt(skills[5].DifficultyValue()) * difficulty_multiplier; + double readingLowARRating = Math.Sqrt(skills[4].DifficultyValue()) * DIFFICULTY_MULTIPLIER; + double readingHighARRating = Math.Sqrt(skills[5].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double readingSlidersRating = 0; - double hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * difficulty_multiplier; + double hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; @@ -64,8 +63,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat flashlightRating *= 0.7; } - double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; - double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; + double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating); + double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating); // Cognition double baseFlashlightPerformance = 0.0; @@ -73,10 +72,10 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; double baseReadingLowARPerformance = Math.Pow(readingLowARRating, 2.5) * 17.0; - double baseReadingHighARPerformance = Math.Pow(5 * Math.Max(1, readingHighARRating / 0.0675) - 4, 3) / 100000; - double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SumPower) + Math.Pow(baseReadingHighARPerformance, SumPower), 1.0 / SumPower); + double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating); + double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SUM_POWER) + Math.Pow(baseReadingHighARPerformance, SUM_POWER), 1.0 / SUM_POWER); - double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FLSumPower) + Math.Pow(baseReadingARPerformance, FLSumPower), 1.0 / FLSumPower); + double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FL_SUM_POWER) + Math.Pow(baseReadingARPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER); double baseReadingHiddenPerformance = 0; if (mods.Any(h => h is OsuModHidden)) @@ -86,6 +85,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseReadingNonARPerformance = baseReadingHiddenPerformance + baseReadingSliderPerformance; double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; + double drainRate = beatmap.Difficulty.DrainRate; int maxCombo = beatmap.GetMaxCombo(); @@ -94,16 +94,16 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner); // Limit cognition by full memorisation difficulty - double cognitionPerformance = Math.Pow(Math.Pow(baseFlashlightARPerformance, SumPower) + Math.Pow(baseReadingNonARPerformance, SumPower), 1.0 / SumPower); - double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SumPower) + Math.Pow(baseSpeedPerformance, SumPower), 1.0 / SumPower); + double cognitionPerformance = Math.Pow(Math.Pow(baseFlashlightARPerformance, SUM_POWER) + Math.Pow(baseReadingNonARPerformance, SUM_POWER), 1.0 / SUM_POWER); + double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SUM_POWER) + Math.Pow(baseSpeedPerformance, SUM_POWER), 1.0 / SUM_POWER); double potentialFlashlightPerformance = OsuPerformanceCalculator.ComputePerfectFlashlightValue(flashlightRating, hitCirclesCount + sliderCount); cognitionPerformance = OsuPerformanceCalculator.AdjustCognitionPerformance(cognitionPerformance, mechanicalPerformance, potentialFlashlightPerformance); double basePerformance = Math.Pow( - Math.Pow(mechanicalPerformance, SumPower) + - Math.Pow(cognitionPerformance, SumPower) - , 1.0 / SumPower + Math.Pow(mechanicalPerformance, SUM_POWER) + + Math.Pow(cognitionPerformance, SUM_POWER) + , 1.0 / SUM_POWER ); double starRating = basePerformance > 0.00001 @@ -115,6 +115,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; + //var test = ((ReadingHighAR)skills[5]).GetAimSpeed(); + OsuDifficultyAttributes attributes = new OsuDifficultyAttributes { StarRating = starRating, @@ -128,7 +130,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat HiddenDifficulty = hiddenRating, FlashlightDifficulty = flashlightRating, SliderFactor = sliderFactor, - ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, + ApproachRate = IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, 1800, 1200, 450), OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, MaxCombo = maxCombo, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 979bda168f4a..384c1b3df7e8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -43,6 +43,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s effectiveMissCount = calculateEffectiveMissCount(osuAttributes); double multiplier = PERFORMANCE_BASE_MULTIPLIER; + double power = OsuDifficultyCalculator.SUM_POWER; if (score.Mods.Any(m => m is OsuModNoFail)) multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount); @@ -62,8 +63,6 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits); } - double power = OsuDifficultyCalculator.SumPower; - double aimValue = computeAimValue(score, osuAttributes); double speedValue = computeSpeedValue(score, osuAttributes); double mechanicalValue = Math.Pow(Math.Pow(aimValue, power) + Math.Pow(speedValue, power), 1.0 / power); @@ -75,12 +74,22 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double flashlightValue = potentialFlashlightValue; if (!score.Mods.Any(h => h is OsuModFlashlight)) flashlightValue = 0.0; - double readingARValue = computeReadingARValue(score, osuAttributes); + + double lowARValue = computeReadingLowARValue(score, osuAttributes); + double readingHDValue = computeReadingHiddenValue(score, osuAttributes); + double readingSlidersValue = 0; + + double highARValue = computeReadingHighARValue(score, osuAttributes); + + double readingARValue = Math.Pow( + Math.Pow(lowARValue, power) + + Math.Pow(highARValue, power), 1.0 / power); + // Reduce AR reading bonus if FL is present - double flPower = OsuDifficultyCalculator.FLSumPower; + double flPower = OsuDifficultyCalculator.FL_SUM_POWER; double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, flPower) + Math.Pow(readingARValue, flPower), 1.0 / flPower); - double readingNonARValue = computeReadingNonARValue(score, osuAttributes); + double readingNonARValue = readingHDValue + readingSlidersValue; double cognitionValue = Math.Pow(Math.Pow(flashlightARValue, power) + Math.Pow(readingNonARValue, power), 1.0 / power); cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialFlashlightValue); @@ -106,7 +115,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double aimValue = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty); double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -143,7 +152,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib if (score.Mods.Any(h => h is OsuModRelax)) return 0.0; - double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -245,25 +254,6 @@ public static double ComputePerfectFlashlightValue(double flashlightDifficulty, return flashlightValue; } - private double computeReadingARValue(ScoreInfo score, OsuDifficultyAttributes attributes) - { - //double readingARValue = Math.Max(computeReadingLowARValue(score, attributes), computeReadingHighARValue(score, attributes)); - double power = OsuDifficultyCalculator.SumPower; - double readingValue = Math.Pow( - Math.Pow(computeReadingLowARValue(score, attributes), power) + - Math.Pow(computeReadingHighARValue(score, attributes), power), 1.0 / power); - - return readingValue; - } - - private double computeReadingNonARValue(ScoreInfo score, OsuDifficultyAttributes attributes) - { - double readingHDValue = computeReadingHiddenValue(score, attributes); - double readingSlidersValue = 0; - - return readingHDValue + readingSlidersValue; - } - private double computeReadingLowARValue(ScoreInfo score, OsuDifficultyAttributes attributes) { double rawReading = attributes.ReadingDifficultyLowAR; @@ -289,8 +279,7 @@ private double computeReadingLowARValue(ScoreInfo score, OsuDifficultyAttributes private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - // Copied from aim - double highARValue = Math.Pow(5.0 * Math.Max(1.0, attributes.ReadingDifficultyHighAR / 0.0675) - 4.0, 3.0) / 100000.0; + double highARValue = OsuStrainSkill.DifficultyToPerformance(attributes.ReadingDifficultyHighAR); // High AR should have length bonus, even more agressive than normal aim double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + @@ -303,11 +292,48 @@ private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttribute highARValue *= getComboScalingFactor(attributes); - highARValue *= accuracy * accuracy; - // It is important to consider accuracy difficulty when scaling with accuracy. - highARValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; + // Approximate how much of high AR difficulty is aim + double aimPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty); + double speedPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); + + double aimRatio = aimPerformance / (aimPerformance + speedPerformance); + + // Aim part calculation + double aimPartValue = highARValue * aimRatio; + { + // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. + double estimateDifficultSliders = attributes.SliderCount * 0.15; + + if (attributes.SliderCount > 0) + { + double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); + double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; + aimPartValue *= sliderNerfFactor; + } + + aimPartValue *= accuracy; + // It is important to consider accuracy difficulty when scaling with accuracy. + aimPartValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; + } + + // Speed part calculation + double speedPartValue = highARValue * (1 - aimRatio); + { + // Calculate accuracy assuming the worst case scenario + double relevantTotalDiff = totalHits - attributes.SpeedNoteCount; + double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); + double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); + 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); + + // 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); + + // Scale the speed value with # of 50s to punish doubletapping. + speedPartValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); + } - return highARValue; + return aimPartValue + speedPartValue; } private double computeReadingHiddenValue(ScoreInfo score, OsuDifficultyAttributes attributes) @@ -316,6 +342,7 @@ private double computeReadingHiddenValue(ScoreInfo score, OsuDifficultyAttribute return 0.0; double rawReading = attributes.HiddenDifficulty; + //double readingValue = Math.Pow(rawReading, 2.0) * 25.0; double readingValue = Math.Pow(rawReading, 2.0) * 25.0; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 2ba5c3cc10f7..19478cd753a6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -95,12 +95,26 @@ public class OsuDifficultyHitObject : DifficultyHitObject /// public IList OverlapObjects { get; private set; } - private readonly OsuHitObject? lastLastObject; - private readonly OsuHitObject lastObject; + /// + /// Time in ms between appearence of this and moment to click on it. + /// public readonly double Preempt; + + /// + /// Preempt of follow line for this adjusted by clockrate. + /// Will be equal to 0 if object is New Combo. + /// public readonly double FollowLineTime; + + /// + /// Playback rate of beatmap. + /// Will be equal 1.5 on DT and 0.75 on HT. + /// public readonly double ClockRate; + private readonly OsuHitObject? lastLastObject; + private readonly OsuHitObject lastObject; + public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject? lastLastObject, double clockRate, List objects, int index) : base(hitObject, lastObject, clockRate, objects, index) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 15b20a557279..bee9832b1b4b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -7,6 +7,7 @@ using osu.Game.Rulesets.Mods; using System.Linq; using osu.Framework.Utils; +using System.Xml.Linq; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -67,5 +68,15 @@ public override double DifficultyValue() return difficulty * DifficultyMultiplier; } + + /// + /// Converts difficulty value from to base performance. + /// + public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0; + + /// + /// Converts base performance to difficulty value.s + /// + public static double PerformanceToDifficulty(double performance) => (Math.Pow(100000.0 * performance, 1.0 / 3.0) + 4.0) / 5.0 * 0.0675; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 13fc1ef8bb0c..1e5a4f73ac77 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -9,7 +9,6 @@ using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; -using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -75,65 +74,15 @@ public override double DifficultyValue() } } - public class ReadingHighAR : GraphSkill + public class ReadingHidden : OsuStrainSkill { - public ReadingHighAR(Mod[] mods) - : base(mods) - { - aimComponent = new HighARAimComponent(mods); - speedComponent = new HighARSpeedComponent(mods); - } - - private HighARAimComponent aimComponent; - private HighARSpeedComponent speedComponent; - - private readonly List difficulties = new List(); - - public override void Process(DifficultyHitObject current) - { - aimComponent.Process(current); - speedComponent.Process(current); - - double power = OsuDifficultyCalculator.SumPower; - double mergedDifficulty = Math.Pow( - Math.Pow(aimComponent.CurrentSectionPeak, power) + - Math.Pow(speedComponent.CurrentSectionPeak, power), 1.0 / power); - - difficulties.Add(mergedDifficulty); - - if (current.Index == 0) - CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; - - while (current.StartTime > CurrentSectionEnd) - { - StrainPeaks.Add(CurrentSectionPeak); - CurrentSectionPeak = 0; - CurrentSectionEnd += SectionLength; - } - - CurrentSectionPeak = Math.Max(mergedDifficulty, CurrentSectionPeak); - } - public override double DifficultyValue() - { - double power = OsuDifficultyCalculator.SumPower; - return Math.Pow( - Math.Pow(aimComponent.DifficultyValue(), power) + - Math.Pow(speedComponent.DifficultyValue(), power), 1.0 / power); - } - } - - public class HighARAimComponent : OsuStrainSkill - { - public HighARAimComponent(Mod[] mods) + public ReadingHidden(Mod[] mods) : base(mods) { } private double currentStrain; - // private double currentRhythm; - - //private double skillMultiplier => 13; - private double skillMultiplier => 14; + private double skillMultiplier => 5; private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); @@ -144,105 +93,14 @@ protected override double StrainValueAt(DifficultyHitObject current) { currentStrain *= strainDecay(current.DeltaTime); - double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true, ((OsuDifficultyHitObject)current).Preempt); - aimDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current, true); - aimDifficulty *= skillMultiplier; - - double totalStrain = currentStrain; - currentStrain += aimDifficulty; - - // Warning: this line is unstable, so increasing amount of objects can decrease pp - totalStrain += aimDifficulty * (1 + ReadingEvaluator.EvaluateLowDensityBonusOf(current)); + // We're not using slider aim because we assuming that HD doesn't makes sliders harder (what is not true, but we will ignore this for now) + double hiddenDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false); + hiddenDifficulty *= ReadingHiddenEvaluator.EvaluateDifficultyOf(current); + hiddenDifficulty *= skillMultiplier; + currentStrain += hiddenDifficulty; - //Console.WriteLine($"{current.StartTime} - {ReadingEvaluator.EvaluateLowDensityBonusOf(current)}"); - - return totalStrain; - } - } - - public class HighARSpeedComponent : OsuStrainSkill - { - private double skillMultiplier => 675; - private double strainDecayBase => 0.3; - - private double currentStrain; - private double currentRhythm; - - public HighARSpeedComponent(Mod[] mods) - : base(mods) - { - } - - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); - - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * strainDecay(time - current.Previous(0).StartTime); - - protected override double StrainValueAt(DifficultyHitObject current) - { - OsuDifficultyHitObject currODHO = (OsuDifficultyHitObject)current; - - currentStrain *= strainDecay(currODHO.StrainTime); - - double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; - speedDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current, false); - currentStrain += speedDifficulty; - - currentRhythm = currODHO.RhythmDifficulty; - // currentRhythm *= currentRhythm; // Squaring is broken cuz rhythm is broken (((( - - double totalStrain = currentStrain * currentRhythm; - return totalStrain; - } - } - - public class ReadingHidden : GraphSkill - { - public ReadingHidden(Mod[] mods) - : base(mods) - { - } - - private readonly List difficulties = new List(); - private double skillMultiplier => 2.3; - - public override void Process(DifficultyHitObject current) - { - double currentDifficulty = ReadingEvaluator.EvaluateHiddenDifficultyOf(current) * skillMultiplier; - - difficulties.Add(currentDifficulty); - - if (current.Index == 0) - CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; - - while (current.StartTime > CurrentSectionEnd) - { - StrainPeaks.Add(CurrentSectionPeak); - CurrentSectionPeak = 0; - CurrentSectionEnd += SectionLength; - } - - CurrentSectionPeak = Math.Max(currentDifficulty, CurrentSectionPeak); - } - - 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); - - List values = peaks.OrderByDescending(d => d).ToList(); - - // 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); - } - - return difficulty; + return currentStrain; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs new file mode 100644 index 000000000000..06fe2d3a8452 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -0,0 +1,146 @@ +// 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 osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Difficulty.Evaluators; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; + +namespace osu.Game.Rulesets.Osu.Difficulty.Skills +{ + public class ReadingHighAR : GraphSkill + { + public ReadingHighAR(Mod[] mods) + : base(mods) + { + aimComponent = new HighARAimComponent(mods); + speedComponent = new HighARSpeedComponent(mods); + + aimComponentNoAdjust = new HighARAimComponent(mods, false); + } + + private HighARAimComponent aimComponent; + private HighARAimComponent aimComponentNoAdjust; + private HighARSpeedComponent speedComponent; + + private readonly List difficulties = new List(); + + public override void Process(DifficultyHitObject current) + { + aimComponent.Process(current); + speedComponent.Process(current); + + aimComponentNoAdjust.Process(current); + + double power = OsuDifficultyCalculator.SUM_POWER; + double mergedDifficulty = Math.Pow( + Math.Pow(aimComponent.CurrentSectionPeak, power) + + Math.Pow(speedComponent.CurrentSectionPeak, power), 1.0 / power); + + difficulties.Add(mergedDifficulty); + + if (current.Index == 0) + CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; + + while (current.StartTime > CurrentSectionEnd) + { + StrainPeaks.Add(CurrentSectionPeak); + CurrentSectionPeak = 0; + CurrentSectionEnd += SectionLength; + } + + CurrentSectionPeak = Math.Max(mergedDifficulty, CurrentSectionPeak); + } + public override double DifficultyValue() + { + Console.WriteLine($"Degree of High AR Complexity = {aimComponent.DifficultyValue() / aimComponentNoAdjust.DifficultyValue():0.##}"); + + // Simulating summing + double aimValue = Math.Sqrt(aimComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; + double speedValue = Math.Sqrt(speedComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; + + double aimPerformance = OsuStrainSkill.DifficultyToPerformance(aimValue); + double speedPerformance = OsuStrainSkill.DifficultyToPerformance(speedValue); + + double power = OsuDifficultyCalculator.SUM_POWER; + double totalPerformance = Math.Pow(Math.Pow(aimPerformance, power) + Math.Pow(speedPerformance, power), 1.0 / power); + + double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance); + + return Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); + } + } + + public class HighARAimComponent : OsuStrainSkill + { + public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) + : base(mods) + { + this.adjustHighAR = adjustHighAR; + } + + private bool adjustHighAR; + private double currentStrain; + + private double skillMultiplier => 19; + private double strainDecayBase => 0.15; + + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime); + + protected override double StrainValueAt(DifficultyHitObject current) + { + currentStrain *= strainDecay(current.DeltaTime); + + double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true); + aimDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR); + aimDifficulty *= skillMultiplier; + + double totalStrain = currentStrain; + + currentStrain += aimDifficulty; + totalStrain += aimDifficulty; + + // Console.WriteLine($"{current.BaseObject.StartTime},{aimDifficulty:0.#}"); + + return totalStrain; + } + } + + public class HighARSpeedComponent : OsuStrainSkill + { + private double skillMultiplier => 850; + private double strainDecayBase => 0.3; + + private double currentStrain; + private double currentRhythm; + + public HighARSpeedComponent(Mod[] mods) + : base(mods) + { + } + + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * strainDecay(time - current.Previous(0).StartTime); + + protected override double StrainValueAt(DifficultyHitObject current) + { + OsuDifficultyHitObject currODHO = (OsuDifficultyHitObject)current; + + currentStrain *= strainDecay(currODHO.StrainTime); + + double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; + speedDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current, false); + currentStrain += speedDifficulty; + + currentRhythm = currODHO.RhythmDifficulty; + double totalStrain = currentStrain * currentRhythm; + return totalStrain; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 032f105dedae..506145568ecd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -98,7 +98,7 @@ public int RepeatCount set { repeatCount = value; - endPositionCache.Invalidate(); + updateNestedPositions(); } } @@ -165,7 +165,7 @@ public double SliderVelocityMultiplier public Slider() { SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples(); - Path.Version.ValueChanged += _ => endPositionCache.Invalidate(); + Path.Version.ValueChanged += _ => updateNestedPositions(); } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) From 639f877d043c3f2ee112e3c4781303b196cda7d1 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 4 Feb 2024 02:30:11 +0200 Subject: [PATCH 43/96] Minor SR adjust for high AR bonus --- .../Difficulty/OsuPerformanceCalculator.cs | 15 +++++++-------- .../Difficulty/Skills/ReadingHighAR.cs | 9 +++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 384c1b3df7e8..92b9476a13a3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -113,12 +113,13 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s }; } + public static double CalculateDefaultLengthBonus(int objectsCount) => 0.95 + 0.4 * Math.Min(1.0, objectsCount / 2000.0) + (objectsCount > 2000 ? Math.Log10(objectsCount / 2000.0) * 0.5 : 0.0); + private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes) { double aimValue = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty); - double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); + double lengthBonus = CalculateDefaultLengthBonus(totalHits); aimValue *= lengthBonus; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. @@ -154,8 +155,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); - double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); + double lengthBonus = CalculateDefaultLengthBonus(totalHits); speedValue *= lengthBonus; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. @@ -281,10 +281,9 @@ private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttribute { double highARValue = OsuStrainSkill.DifficultyToPerformance(attributes.ReadingDifficultyHighAR); - // High AR should have length bonus, even more agressive than normal aim - double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); - highARValue *= lengthBonus * lengthBonus; + // Second half of length bonus, to match mechanical skills SR scaling + double lengthBonus = CalculateDefaultLengthBonus(totalHits); + highARValue *= lengthBonus; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index 06fe2d3a8452..114fb2d54814 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -27,6 +28,7 @@ public ReadingHighAR(Mod[] mods) private HighARSpeedComponent speedComponent; private readonly List difficulties = new List(); + private int objectsCount = 0; public override void Process(DifficultyHitObject current) { @@ -35,6 +37,9 @@ public override void Process(DifficultyHitObject current) aimComponentNoAdjust.Process(current); + if (current.BaseObject is not Spinner) + objectsCount++; + double power = OsuDifficultyCalculator.SUM_POWER; double mergedDifficulty = Math.Pow( Math.Pow(aimComponent.CurrentSectionPeak, power) + @@ -68,6 +73,10 @@ public override double DifficultyValue() double power = OsuDifficultyCalculator.SUM_POWER; double totalPerformance = Math.Pow(Math.Pow(aimPerformance, power) + Math.Pow(speedPerformance, power), 1.0 / power); + // First half of length bonus is in SR to not inflate short AR11 maps + double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount); + totalPerformance *= lengthBonus; + double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance); return Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); From e6f1a4067d2e2a079e1099be238eb9d59b34d7ad Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 12 Feb 2024 19:55:52 +0200 Subject: [PATCH 44/96] Change scaling to make high AR woth more on low SR --- .../Evaluators/ReadingHighAREvaluator.cs | 39 ++++++++++--------- .../Difficulty/Skills/ReadingHighAR.cs | 27 ++++++++----- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs index aea7f15ab292..c320b4c118bd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs @@ -18,7 +18,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool appl { var currObj = (OsuDifficultyHitObject)current; - double result = highArCurve(currObj.Preempt); + double result = GetDifficulty(currObj.Preempt); if (applyAdjust) { @@ -27,7 +27,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool appl // follow lines make high AR easier, so apply nerf if object isn't new combo inpredictability *= 1 + 0.1 * (800 - currObj.FollowLineTime) / 800; - result *= 0.85 + 1 * inpredictability; + result *= 0.9 + 1 * inpredictability; result *= 1.05 - 0.4 * EvaluateFieryAnglePunishmentOf(current); } @@ -119,29 +119,32 @@ private static double getAlternateWithZeroAnglePunishment(double a1, double a2, return zeroFactor * angleSimilarityFactor * angleSharpnessFactor; } - public static double EvaluateLowDensityBonusOf(DifficultyHitObject current) - { - //var currObj = (OsuDifficultyHitObject)current; - - //// Density = 2 in general means 3 notes on screen (it's not including current note) - //double density = CalculateDenstityOf(currObj); - - //// We are considering density = 1.5 as starting point, 1.0 is noticably uncomfy and 0.5 is severely uncomfy - //double bonus = 1.5 - density; - //if (bonus <= 0) return 0; - - //return Math.Pow(bonus, 2); - return 0; - } - + // High AR curve // https://www.desmos.com/calculator/hbj7swzlth - private static double highArCurve(double preempt) + public static double GetDifficulty(double preempt) { double value = Math.Pow(3, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms) return value; } + // This is very accurate on preempt > 300ms, breaking starting somewhere around 120ms + public static double GetPreempt(double difficulty) + { + double fixCoef = difficulty / GetDifficulty(highArCurveReversed(difficulty)); + return highArCurveReversed(difficulty * fixCoef); + } + + // This is an approximation cuz high AR curve is unsolvable + // https://www.desmos.com/calculator/n9vk18bcyh + private static double highArCurveReversed(double value) + { + double helperValue = value / Math.Pow(1 - Math.Pow(1.7, value - 2), 0.45); + double preempt = -(Math.Log(helperValue, 3) - 3) / 0.01; + + return preempt; + } + // We are using mutiply and divide instead of add and subtract, so values won't be negative // https://www.desmos.com/calculator/fv5xerwpd2 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); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index 114fb2d54814..5c070242e4ac 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -29,9 +29,12 @@ public ReadingHighAR(Mod[] mods) private readonly List difficulties = new List(); private int objectsCount = 0; + private double preempt = -1; public override void Process(DifficultyHitObject current) { + if (preempt < 0) preempt = ((OsuDifficultyHitObject)current).Preempt; + aimComponent.Process(current); speedComponent.Process(current); @@ -61,9 +64,16 @@ public override void Process(DifficultyHitObject current) } public override double DifficultyValue() { - Console.WriteLine($"Degree of High AR Complexity = {aimComponent.DifficultyValue() / aimComponentNoAdjust.DifficultyValue():0.##}"); + // Get number how much high AR adjust changed difficulty + double difficultyRatio = aimComponent.DifficultyValue() / aimComponentNoAdjust.DifficultyValue(); + + // Calculate how much preempt should change to account for high AR adjust + double difficulty = ReadingHighAREvaluator.GetDifficulty(preempt) * difficultyRatio; + double adjustedPreempt = ReadingHighAREvaluator.GetPreempt(difficulty); + + Console.WriteLine($"Degree of High AR Complexity = {difficultyRatio:0.##}, {preempt:0} -> {adjustedPreempt:0}"); - // Simulating summing + // Simulating summing to get the most correct value possible double aimValue = Math.Sqrt(aimComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; double speedValue = Math.Sqrt(speedComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; @@ -78,8 +88,10 @@ public override double DifficultyValue() totalPerformance *= lengthBonus; double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance); + double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); - return Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); + return 75 * Math.Sqrt(difficultyValue * difficulty); + // return difficultyValue; } } @@ -94,7 +106,7 @@ public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) private bool adjustHighAR; private double currentStrain; - private double skillMultiplier => 19; + private double skillMultiplier => 18.5; private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); @@ -109,14 +121,9 @@ protected override double StrainValueAt(DifficultyHitObject current) aimDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR); aimDifficulty *= skillMultiplier; - double totalStrain = currentStrain; - currentStrain += aimDifficulty; - totalStrain += aimDifficulty; - - // Console.WriteLine($"{current.BaseObject.StartTime},{aimDifficulty:0.#}"); - return totalStrain; + return currentStrain; } } From 9e6ae3587ae77d857052e1eb6e75e5e2d952bffc Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 23 Feb 2024 17:44:56 +0200 Subject: [PATCH 45/96] Changes to highAR and angle nerf HighAR now have passive strain bonus to buff low SR maps Angle nerf now applied inplace (copied from HD calc) --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 152 +++++++++--------- .../Difficulty/Skills/Reading.cs | 1 - .../Difficulty/Skills/ReadingHighAR.cs | 25 ++- 3 files changed, 82 insertions(+), 96 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 90fb1758d0bc..219f325ae002 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -3,10 +3,10 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators @@ -20,23 +20,90 @@ public static class ReadingEvaluator public static double CalculateDenstityOf(OsuDifficultyHitObject currObj) { - double pastObjectDifficultyInfluence = 0; + double density = 0; + double densityAnglesNerf = -2; // we have threshold of 2, so 2 or same angles won't be punished - foreach (var loopObj in retrievePastVisibleObjects(currObj)) + OsuDifficultyHitObject? prevObj0 = null; + OsuDifficultyHitObject? prevObj1 = null; + OsuDifficultyHitObject? prevObj2 = null; + + double prevConstantAngle = 0; + + foreach (var loopObj in retrievePastVisibleObjects(currObj).Reverse()) { double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. + // For HD: it's not subtracting anything cuz it's multiplied by the aim difficulty anyways. loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 60) / 10); - //double timeBetweenCurrAndLoopObj = (currObj.BaseObject.StartTime - loopObj.BaseObject.StartTime) / clockRateEstimate; + // Reduce density bonus for this object if they're too apart in time + // Nerf starts on 1500ms and reaches maximum (*=0) on 3000ms double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime; loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); - pastObjectDifficultyInfluence += loopDifficulty; + if (prevObj0.IsNull()) + { + prevObj0 = loopObj; + continue; + } + + density += loopDifficulty; + + // Angles nerf + + if (loopObj.Angle.IsNotNull() && prevObj0.Angle.IsNotNull()) + { + double angleDifference = Math.Abs(prevObj0.Angle.Value - loopObj.Angle.Value); + + // Nerf alternating angles case + if (prevObj1.IsNotNull() && prevObj2.IsNotNull() && prevObj1.Angle.IsNotNull() && prevObj2.Angle.IsNotNull()) + { + // Normalized difference + double angleDifference1 = Math.Abs(prevObj1.Angle.Value - loopObj.Angle.Value) / Math.PI; + double angleDifference2 = Math.Abs(prevObj2.Angle.Value - prevObj0.Angle.Value) / Math.PI; + + // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 + double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); + + // Be sure to nerf only same rhythms + double rhythmFactor = 1 - getRhythmDifference(loopObj.StrainTime, prevObj0.StrainTime); // 0 on different rhythm, 1 on same rhythm + rhythmFactor *= 1 - getRhythmDifference(prevObj0.StrainTime, prevObj1.StrainTime); + rhythmFactor *= 1 - getRhythmDifference(prevObj1.StrainTime, prevObj2.StrainTime); + + double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevObj0.Angle.Value) / Math.PI; + + double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); + + prevAngleAdjust *= alternatingFactor; // Nerf if alternating + prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms + prevAngleAdjust *= acuteAngleFactor; + + angleDifference -= prevAngleAdjust; + } + + // Reduce angles nerf if objects are too apart in time + // Angle nerf is starting being reduced from 200ms (150BPM jump) and it reduced to 0 on 2000ms + double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - 200) / (2000 - 200), 0, 1); + + // Current angle nerf. Angle difference less than 15 degrees is considered the same + double currConstantAngle = Math.Cos(4 * Math.Min(Math.PI / 12, angleDifference)) * longIntervalFactor; + + // Apply the nerf only when it's repeated + double currentAngleNerf = Math.Min(currConstantAngle, prevConstantAngle); + + densityAnglesNerf += Math.Min(currentAngleNerf, loopDifficulty); + prevConstantAngle = currConstantAngle; + } + + prevObj2 = prevObj1; + prevObj1 = prevObj0; + prevObj0 = loopObj; } - return pastObjectDifficultyInfluence; + // Apply angles nerf + density -= Math.Max(0, densityAnglesNerf); + return density; } public static double CalculateOverlapDifficultyOf(OsuDifficultyHitObject currObj) @@ -71,8 +138,6 @@ public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) screenOverlapDifficulty = Math.Max(0, screenOverlapDifficulty - 0.5); // make overlap value =1 cost significantly less double overlapBonus = overlap_multiplier * screenOverlapDifficulty * difficulty; - - difficulty *= getConstantAngleNerfFactor(currObj); difficulty += overlapBonus; //difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty; @@ -185,77 +250,6 @@ private static IEnumerable retrievePastVisibleObjects(Os } } - private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current) - { - const double time_limit = 2000; - const double time_limit_low = 200; - - double constantAngleCount = 0; - int index = 0; - double currentTimeGap = 0; - - OsuDifficultyHitObject prevLoopObj = current; - - OsuDifficultyHitObject? prevLoopObj1 = null; - OsuDifficultyHitObject? prevLoopObj2 = null; - - double prevConstantAngle = 0; - - while (currentTimeGap < time_limit) - { - var loopObj = (OsuDifficultyHitObject)current.Previous(index); - - if (loopObj.IsNull()) - break; - - double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - time_limit_low) / (time_limit - time_limit_low), 0, 1); - - if (loopObj.Angle.IsNotNull() && prevLoopObj.Angle.IsNotNull()) - { - double angleDifference = Math.Abs(prevLoopObj.Angle.Value - loopObj.Angle.Value); - - // Nerf alternating angles case - if (prevLoopObj1.IsNotNull() && prevLoopObj2.IsNotNull() && prevLoopObj1.Angle.IsNotNull() && prevLoopObj2.Angle.IsNotNull()) - { - // Normalized difference - double angleDifference1 = Math.Abs(prevLoopObj1.Angle.Value - loopObj.Angle.Value) / Math.PI; - double angleDifference2 = Math.Abs(prevLoopObj2.Angle.Value - prevLoopObj.Angle.Value) / Math.PI; - - // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 - double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); - - // Be sure to nerf only same rhythms - double rhythmFactor = 1 - getRhythmDifference(loopObj.StrainTime, prevLoopObj.StrainTime); // 0 on different rhythm, 1 on same rhythm - rhythmFactor *= 1 - getRhythmDifference(prevLoopObj.StrainTime, prevLoopObj1.StrainTime); - rhythmFactor *= 1 - getRhythmDifference(prevLoopObj1.StrainTime, prevLoopObj2.StrainTime); - - double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevLoopObj.Angle.Value) / Math.PI; - - double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); - - prevAngleAdjust *= alternatingFactor; // Nerf if alternating - prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms - prevAngleAdjust *= acuteAngleFactor; - - angleDifference -= prevAngleAdjust; - } - - double currConstantAngle = Math.Cos(4 * Math.Min(Math.PI / 8, angleDifference)) * longIntervalFactor; - constantAngleCount += Math.Min(currConstantAngle, prevConstantAngle); - prevConstantAngle = currConstantAngle; - } - - currentTimeGap = current.StartTime - loopObj.StartTime; - index++; - - prevLoopObj2 = prevLoopObj1; - prevLoopObj1 = prevLoopObj; - prevLoopObj = loopObj; - } - - return Math.Pow(Math.Min(1, 2 / constantAngleCount), 2); - } - private static double getTimeNerfFactor(double deltaTime) { return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 1e5a4f73ac77..3fbac64215ac 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 => 2.3; private double skillMultiplier => 2; public ReadingLowAR(Mod[] mods) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index 5c070242e4ac..6e3049aec67d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -64,15 +64,6 @@ public override void Process(DifficultyHitObject current) } public override double DifficultyValue() { - // Get number how much high AR adjust changed difficulty - double difficultyRatio = aimComponent.DifficultyValue() / aimComponentNoAdjust.DifficultyValue(); - - // Calculate how much preempt should change to account for high AR adjust - double difficulty = ReadingHighAREvaluator.GetDifficulty(preempt) * difficultyRatio; - double adjustedPreempt = ReadingHighAREvaluator.GetPreempt(difficulty); - - Console.WriteLine($"Degree of High AR Complexity = {difficultyRatio:0.##}, {preempt:0} -> {adjustedPreempt:0}"); - // Simulating summing to get the most correct value possible double aimValue = Math.Sqrt(aimComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; double speedValue = Math.Sqrt(speedComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; @@ -83,15 +74,15 @@ public override double DifficultyValue() double power = OsuDifficultyCalculator.SUM_POWER; double totalPerformance = Math.Pow(Math.Pow(aimPerformance, power) + Math.Pow(speedPerformance, power), 1.0 / power); - // First half of length bonus is in SR to not inflate short AR11 maps + // First half of length bonus is in SR to not inflate Star Rating short AR11 maps double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount); totalPerformance *= lengthBonus; double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance); double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); - return 75 * Math.Sqrt(difficultyValue * difficulty); - // return difficultyValue; + // have the same value as difficultyValue at 500pp point + return 75 * Math.Sqrt(difficultyValue); } } @@ -106,7 +97,8 @@ public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) private bool adjustHighAR; private double currentStrain; - private double skillMultiplier => 18.5; + private double skillMultiplier => 17.8; + private double defaultValueMultiplier => 30; private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); @@ -118,12 +110,13 @@ protected override double StrainValueAt(DifficultyHitObject current) currentStrain *= strainDecay(current.DeltaTime); double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true); - aimDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR); + double readingDifficulty = ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR); + aimDifficulty *= Math.Pow(readingDifficulty, 2); aimDifficulty *= skillMultiplier; currentStrain += aimDifficulty; - return currentStrain; + return currentStrain + defaultValueMultiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR); } } @@ -151,7 +144,7 @@ protected override double StrainValueAt(DifficultyHitObject current) currentStrain *= strainDecay(currODHO.StrainTime); double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; - speedDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current, false); + speedDifficulty *= Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, false), 2); currentStrain += speedDifficulty; currentRhythm = currODHO.RhythmDifficulty; From 5d4c78239bb03a94b1ff5dbfab275410a6ce831b Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 23 Feb 2024 19:52:00 +0200 Subject: [PATCH 46/96] added density aim multiplier --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 10 +++---- .../Evaluators/ReadingHighAREvaluator.cs | 19 +----------- .../Difficulty/OsuDifficultyCalculator.cs | 2 +- .../Difficulty/OsuPerformanceCalculator.cs | 2 +- .../Difficulty/Skills/OsuStrainSkill.cs | 4 +++ .../Difficulty/Skills/Reading.cs | 30 +++++++++++++------ .../Difficulty/Skills/ReadingHighAR.cs | 24 +++++---------- .../Difficulty/Skills/Speed.cs | 8 ++--- 8 files changed, 44 insertions(+), 55 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 219f325ae002..596083c8f870 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -18,8 +18,9 @@ public static class ReadingEvaluator private const double overlap_multiplier = 0.8; - public static double CalculateDenstityOf(OsuDifficultyHitObject currObj) + public static double EvaluateDenstityOf(DifficultyHitObject current) { + var currObj = (OsuDifficultyHitObject)current; double density = 0; double densityAnglesNerf = -2; // we have threshold of 2, so 2 or same angles won't be punished @@ -34,8 +35,7 @@ public static double CalculateDenstityOf(OsuDifficultyHitObject currObj) double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - // For HD: it's not subtracting anything cuz it's multiplied by the aim difficulty anyways. - loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 60) / 10); + loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 30) / 10); // Reduce density bonus for this object if they're too apart in time // Nerf starts on 1500ms and reaches maximum (*=0) on 3000ms @@ -123,14 +123,14 @@ public static double CalculateOverlapDifficultyOf(OsuDifficultyHitObject currObj return screenOverlapDifficulty; } - public static double EvaluateDensityDifficultyOf(DifficultyHitObject current) + public static double EvaluateDifficultyOf(DifficultyHitObject current) { if (current.BaseObject is Spinner || current.Index == 0) return 0; var currObj = (OsuDifficultyHitObject)current; - double pastObjectDifficultyInfluence = CalculateDenstityOf(currObj); + double pastObjectDifficultyInfluence = EvaluateDenstityOf(current); double screenOverlapDifficulty = CalculateOverlapDifficultyOf(currObj); double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence)), 2.3); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs index c320b4c118bd..949860aa765a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs @@ -123,28 +123,11 @@ private static double getAlternateWithZeroAnglePunishment(double a1, double a2, // https://www.desmos.com/calculator/hbj7swzlth public static double GetDifficulty(double preempt) { - double value = Math.Pow(3, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms + double value = Math.Pow(3.5, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms) return value; } - // This is very accurate on preempt > 300ms, breaking starting somewhere around 120ms - public static double GetPreempt(double difficulty) - { - double fixCoef = difficulty / GetDifficulty(highArCurveReversed(difficulty)); - return highArCurveReversed(difficulty * fixCoef); - } - - // This is an approximation cuz high AR curve is unsolvable - // https://www.desmos.com/calculator/n9vk18bcyh - private static double highArCurveReversed(double value) - { - double helperValue = value / Math.Pow(1 - Math.Pow(1.7, value - 2), 0.45); - double preempt = -(Math.Log(helperValue, 3) - 3) / 0.01; - - return preempt; - } - // We are using mutiply and divide instead of add and subtract, so values won't be negative // https://www.desmos.com/calculator/fv5xerwpd2 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); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 7b329ce173bb..c04455f23254 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -71,7 +71,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat if (mods.Any(h => h is OsuModFlashlight)) baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; - double baseReadingLowARPerformance = Math.Pow(readingLowARRating, 2.5) * 17.0; + double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating); double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating); double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SUM_POWER) + Math.Pow(baseReadingHighARPerformance, SUM_POWER), 1.0 / SUM_POWER); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 92b9476a13a3..23045f4331d2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -261,7 +261,7 @@ private double computeReadingLowARValue(ScoreInfo score, OsuDifficultyAttributes if (score.Mods.Any(m => m is OsuModTouchDevice)) rawReading = Math.Pow(rawReading, 0.8); - double readingValue = Math.Pow(rawReading, 2.5) * 17.0; + double readingValue = ReadingLowAR.DifficultyToPerformance(rawReading); // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 998eed1f1a8b..353ccc4d8083 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -35,6 +35,10 @@ public abstract class OsuStrainSkill : StrainSkill /// protected virtual double DifficultyMultiplier => DEFAULT_DIFFICULTY_MULTIPLIER; + protected virtual double StrainDecayBase => 0.15; + + protected double StrainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); + protected OsuStrainSkill(Mod[] mods) : base(mods) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 3fbac64215ac..592db63144c2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -16,18 +16,31 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - private double skillMultiplier => 2; + private double skillMultiplier => 1.3; + private double aimComponentMultiplier => 0.7; + //private double skillMultiplier => 2; public ReadingLowAR(Mod[] mods) : base(mods) { } + private double strainDecayBase => 0.15; + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + private double currentDensityAimStrain = 0; + public override void Process(DifficultyHitObject current) { - double currentDifficulty = ReadingEvaluator.EvaluateDensityDifficultyOf(current) * skillMultiplier; + double densityFactor = Math.Max(0, Math.Pow(ReadingEvaluator.EvaluateDenstityOf(current), 1.5) - 1); + // double density = Math.Max(0, ReadingEvaluator.EvaluateDenstityOf(current)); + currentDensityAimStrain *= strainDecay(current.DeltaTime); + currentDensityAimStrain += densityFactor * AimEvaluator.EvaluateDifficultyOf(current, false) * aimComponentMultiplier; + + double densityReadingDifficulty = ReadingEvaluator.EvaluateDifficultyOf(current); + double totalDensityDifficulty = (currentDensityAimStrain + densityReadingDifficulty) * skillMultiplier; - difficulties.Add(currentDifficulty); + difficulties.Add(totalDensityDifficulty); if (current.Index == 0) CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; @@ -39,7 +52,7 @@ public override void Process(DifficultyHitObject current) CurrentSectionEnd += SectionLength; } - CurrentSectionPeak = Math.Max(currentDifficulty, CurrentSectionPeak); + CurrentSectionPeak = Math.Max(totalDensityDifficulty, CurrentSectionPeak); } private double reducedNoteCount => 5; @@ -71,6 +84,8 @@ public override double DifficultyValue() return difficulty; } + + public static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 3) * 10.0; } public class ReadingHidden : OsuStrainSkill @@ -82,15 +97,12 @@ public ReadingHidden(Mod[] mods) private double currentStrain; private double skillMultiplier => 5; - private double strainDecayBase => 0.15; - - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime); + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); protected override double StrainValueAt(DifficultyHitObject current) { - currentStrain *= strainDecay(current.DeltaTime); + currentStrain *= StrainDecay(current.DeltaTime); // We're not using slider aim because we assuming that HD doesn't makes sliders harder (what is not true, but we will ignore this for now) double hiddenDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index 6e3049aec67d..aa5c8d78faf7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -29,12 +29,9 @@ public ReadingHighAR(Mod[] mods) private readonly List difficulties = new List(); private int objectsCount = 0; - private double preempt = -1; public override void Process(DifficultyHitObject current) { - if (preempt < 0) preempt = ((OsuDifficultyHitObject)current).Preempt; - aimComponent.Process(current); speedComponent.Process(current); @@ -97,17 +94,14 @@ public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) private bool adjustHighAR; private double currentStrain; - private double skillMultiplier => 17.8; - private double defaultValueMultiplier => 30; - private double strainDecayBase => 0.15; - - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + private double skillMultiplier => 17; + private double defaultValueMultiplier => 50; - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime); + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); protected override double StrainValueAt(DifficultyHitObject current) { - currentStrain *= strainDecay(current.DeltaTime); + currentStrain *= StrainDecay(current.DeltaTime); double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true); double readingDifficulty = ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR); @@ -122,8 +116,8 @@ protected override double StrainValueAt(DifficultyHitObject current) public class HighARSpeedComponent : OsuStrainSkill { - private double skillMultiplier => 850; - private double strainDecayBase => 0.3; + private double skillMultiplier => 820; + protected override double StrainDecayBase => 0.3; private double currentStrain; private double currentRhythm; @@ -133,15 +127,13 @@ public HighARSpeedComponent(Mod[] mods) { } - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); - - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * strainDecay(time - current.Previous(0).StartTime); + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * StrainDecay(time - current.Previous(0).StartTime); protected override double StrainValueAt(DifficultyHitObject current) { OsuDifficultyHitObject currODHO = (OsuDifficultyHitObject)current; - currentStrain *= strainDecay(currODHO.StrainTime); + currentStrain *= StrainDecay(currODHO.StrainTime); double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; speedDifficulty *= Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, false), 2); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index edc3302b09ee..9ada2b3c2c39 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class Speed : OsuStrainSkill { private double skillMultiplier => 1375; - private double strainDecayBase => 0.3; + protected override double StrainDecayBase => 0.3; private double currentStrain; private double currentRhythm; @@ -32,15 +32,13 @@ public Speed(Mod[] mods) { } - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); - - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * strainDecay(time - current.Previous(0).StartTime); + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * StrainDecay(time - current.Previous(0).StartTime); protected override double StrainValueAt(DifficultyHitObject current) { OsuDifficultyHitObject currODHO = (OsuDifficultyHitObject)current; - currentStrain *= strainDecay(currODHO.StrainTime); + currentStrain *= StrainDecay(currODHO.StrainTime); currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; currentRhythm = currODHO.RhythmDifficulty; From c8e9602e15eb180378016fdecd94e9a7d27a1a2a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 28 Feb 2024 16:01:42 +0200 Subject: [PATCH 47/96] Update ReadingEvaluator.cs --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 596083c8f870..30f0df50d4be 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -35,7 +35,7 @@ public static double EvaluateDenstityOf(DifficultyHitObject current) double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 30) / 10); + loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 60) / 10); // Reduce density bonus for this object if they're too apart in time // Nerf starts on 1500ms and reaches maximum (*=0) on 3000ms From c2e5d767f9838c3d2d4e97bf4510908083f1c317 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 7 Mar 2024 18:32:54 +0200 Subject: [PATCH 48/96] Fixed reading cap Now it's bound to HDFL difficulty instead of FL This means that adding HD to a AR12 map will not increase pp from nothing --- .../Difficulty/OsuDifficultyAttributes.cs | 6 ++++++ .../Difficulty/OsuDifficultyCalculator.cs | 15 +++++++++------ .../Difficulty/OsuPerformanceCalculator.cs | 19 ++++++++++++------- .../Preprocessing/OsuDifficultyHitObject.cs | 4 ++-- .../Difficulty/Skills/Flashlight.cs | 15 ++++++++++++++- .../Difficulty/Skills/Reading.cs | 2 ++ osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 2 ++ 7 files changed, 47 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 09424e741ae7..a9640e579123 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -62,6 +62,12 @@ public class OsuDifficultyAttributes : DifficultyAttributes [JsonProperty("flashlight_difficulty")] public double FlashlightDifficulty { get; set; } + /// + /// The difficulty corresponding to the flashlight skill with HD (used in capping cognition performance). + /// + [JsonProperty("hidden_flashlight_difficulty")] + public double HiddenFlashlightDifficulty { get; set; } + /// /// Describes how much of is contributed to by hitcircles or sliders. /// A value closer to 1.0 indicates most of is contributed by hitcircles. diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index c04455f23254..b16ad5a21f20 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -47,6 +47,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double readingHighARRating = Math.Sqrt(skills[5].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double readingSlidersRating = 0; double hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * DIFFICULTY_MULTIPLIER; + double hiddenFlashlightRating = Math.Sqrt(skills[7].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; @@ -69,7 +70,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat // Cognition double baseFlashlightPerformance = 0.0; if (mods.Any(h => h is OsuModFlashlight)) - baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; + baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating); double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating); double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating); @@ -79,7 +80,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseReadingHiddenPerformance = 0; if (mods.Any(h => h is OsuModHidden)) - baseReadingHiddenPerformance = Math.Pow(hiddenRating, 2.0) * 25.0; + baseReadingHiddenPerformance = ReadingHidden.DifficultyToPerformance(hiddenRating); double baseReadingSliderPerformance = 0; double baseReadingNonARPerformance = baseReadingHiddenPerformance + baseReadingSliderPerformance; @@ -96,8 +97,10 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat // Limit cognition by full memorisation difficulty double cognitionPerformance = Math.Pow(Math.Pow(baseFlashlightARPerformance, SUM_POWER) + Math.Pow(baseReadingNonARPerformance, SUM_POWER), 1.0 / SUM_POWER); double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SUM_POWER) + Math.Pow(baseSpeedPerformance, SUM_POWER), 1.0 / SUM_POWER); - double potentialFlashlightPerformance = OsuPerformanceCalculator.ComputePerfectFlashlightValue(flashlightRating, hitCirclesCount + sliderCount); - cognitionPerformance = OsuPerformanceCalculator.AdjustCognitionPerformance(cognitionPerformance, mechanicalPerformance, potentialFlashlightPerformance); + + double maxHiddenFlashlightPerformance = OsuPerformanceCalculator.ComputePerfectFlashlightValue(hiddenFlashlightRating, hitCirclesCount + sliderCount); + + cognitionPerformance = OsuPerformanceCalculator.AdjustCognitionPerformance(cognitionPerformance, mechanicalPerformance, maxHiddenFlashlightPerformance); double basePerformance = Math.Pow( @@ -115,8 +118,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate; - //var test = ((ReadingHighAR)skills[5]).GetAimSpeed(); - OsuDifficultyAttributes attributes = new OsuDifficultyAttributes { StarRating = starRating, @@ -129,6 +130,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat ReadingDifficultySliders = readingSlidersRating, HiddenDifficulty = hiddenRating, FlashlightDifficulty = flashlightRating, + HiddenFlashlightDifficulty = hiddenFlashlightRating, SliderFactor = sliderFactor, ApproachRate = IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, 1800, 1200, 450), OverallDifficulty = (80 - hitWindowGreat) / 6, @@ -168,6 +170,7 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo new ReadingLowAR(mods), new ReadingHighAR(mods), new ReadingHidden(mods), + new HiddenFlashlight(mods), }; if (mods.Any(h => h is OsuModFlashlight)) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 23045f4331d2..8e3ab1a2ba75 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -69,8 +69,14 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s // Cognition + // Preferably this value should be used for capping low AR reading performance + // Because it should have lower cap (you can actually see) + // But it will make formula really weird and overcomplicated double potentialFlashlightValue = computeFlashlightValue(score, osuAttributes); + // Get HDFL value for capping reading performance + double potentialHiddenFlashlightValue = computeFlashlightValue(score, osuAttributes, true); + double flashlightValue = potentialFlashlightValue; if (!score.Mods.Any(h => h is OsuModFlashlight)) flashlightValue = 0.0; @@ -91,7 +97,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double readingNonARValue = readingHDValue + readingSlidersValue; double cognitionValue = Math.Pow(Math.Pow(flashlightARValue, power) + Math.Pow(readingNonARValue, power), 1.0 / power); - cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialFlashlightValue); + cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialHiddenFlashlightValue); double accuracyValue = computeAccuracyValue(score, osuAttributes); @@ -222,9 +228,9 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att return accuracyValue; } - private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes) + private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes, bool alwaysUseHD = false) { - double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0; + double flashlightValue = Math.Pow(alwaysUseHD ? attributes.HiddenFlashlightDifficulty : attributes.FlashlightDifficulty, 2.0) * 25.0; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) @@ -246,7 +252,7 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a public static double ComputePerfectFlashlightValue(double flashlightDifficulty, int objectsCount) { - double flashlightValue = Math.Pow(flashlightDifficulty, 2.0) * 25.0; + double flashlightValue = Flashlight.DifficultyToPerformance(flashlightDifficulty); flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, objectsCount / 200.0) + (objectsCount > 200 ? 0.2 * Math.Min(1.0, (objectsCount - 200) / 200.0) : 0.0); @@ -341,8 +347,7 @@ private double computeReadingHiddenValue(ScoreInfo score, OsuDifficultyAttribute return 0.0; double rawReading = attributes.HiddenDifficulty; - //double readingValue = Math.Pow(rawReading, 2.0) * 25.0; - double readingValue = Math.Pow(rawReading, 2.0) * 25.0; + double readingValue = ReadingHidden.DifficultyToPerformance(attributes.HiddenDifficulty); // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) @@ -386,7 +391,7 @@ public static double AdjustCognitionPerformance(double cognitionPerformance, dou // Avoid it being broken on millions of pp, ruins it being continious, but it will never happen on normal circumstances if (capPerformance > 10000 || cognitionPerformance > 10000) cognitionPerformance = Math.Min(capPerformance, cognitionPerformance); - else cognitionPerformance = 100 * softmin(capPerformance / 100, cognitionPerformance / 100, 100); + else cognitionPerformance = 1000 * softmin(capPerformance / 1000, cognitionPerformance / 1000, 100); return cognitionPerformance; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 19478cd753a6..e1431d6978a2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -263,12 +263,12 @@ public double OpacityAt(double time, bool hidden) } double fadeInStartTime = BaseObject.StartTime - BaseObject.TimePreempt; - double fadeInDuration = BaseObject.TimeFadeIn; + double fadeInDuration = BaseObject.TimeFadeInRaw; if (hidden) { // Taken from OsuModHidden. - double fadeOutStartTime = BaseObject.StartTime - BaseObject.TimePreempt + BaseObject.TimeFadeIn; + double fadeOutStartTime = BaseObject.StartTime - BaseObject.TimePreempt + BaseObject.TimeFadeInRaw; double fadeOutDuration = BaseObject.TimePreempt * OsuModHidden.FADE_OUT_DURATION_MULTIPLIER; return Math.Min diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 3d6d3f99c174..9fafeacb9c2b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class Flashlight : StrainSkill { private readonly bool hasHiddenMod; + protected virtual bool HasHiddenMod => hasHiddenMod; public Flashlight(Mod[] mods) : base(mods) @@ -36,11 +37,23 @@ public Flashlight(Mod[] mods) protected override double StrainValueAt(DifficultyHitObject current) { currentStrain *= strainDecay(current.DeltaTime); - currentStrain += FlashlightEvaluator.EvaluateDifficultyOf(current, hasHiddenMod) * skillMultiplier; + currentStrain += FlashlightEvaluator.EvaluateDifficultyOf(current, HasHiddenMod) * skillMultiplier; return currentStrain; } public override double DifficultyValue() => GetCurrentStrainPeaks().Sum() * OsuStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER; + + public static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 2) * 25.0; + } + + public class HiddenFlashlight : Flashlight + { + protected override bool HasHiddenMod => true; + + public HiddenFlashlight(Mod[] mods) + : base(mods) + { + } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 592db63144c2..dcfe624db230 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -113,5 +113,7 @@ protected override double StrainValueAt(DifficultyHitObject current) return currentStrain; } + + public static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 2) * 25.0; } } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 74631400cae9..84487ec0bd39 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -49,6 +49,7 @@ public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPositi public double TimePreempt = 600; public double TimeFadeIn = 400; + public double TimeFadeInRaw = 400; private HitObjectProperty position; @@ -165,6 +166,7 @@ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, I // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN); + TimeFadeInRaw = TimeFadeIn; Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true); } From 4b5d4635c9432da2f306a727709e3dcb6f1c64ad Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 7 Mar 2024 18:34:07 +0200 Subject: [PATCH 49/96] Update Reading.cs --- osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index dcfe624db230..d237ca4eb538 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -114,6 +114,6 @@ protected override double StrainValueAt(DifficultyHitObject current) return currentStrain; } - public static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 2) * 25.0; + public new static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 2) * 25.0; } } From a8b6ae978b037d39336ce867a09478ca28bd304a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 8 Mar 2024 03:02:34 +0200 Subject: [PATCH 50/96] high AR speed nerf --- .../Difficulty/Skills/ReadingHighAR.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index aa5c8d78faf7..531a18a58169 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -95,7 +95,7 @@ public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) private double currentStrain; private double skillMultiplier => 17; - private double defaultValueMultiplier => 50; + private double defaultValueMultiplier => 25; protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); @@ -103,10 +103,8 @@ protected override double StrainValueAt(DifficultyHitObject current) { currentStrain *= StrainDecay(current.DeltaTime); - double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true); - double readingDifficulty = ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR); - aimDifficulty *= Math.Pow(readingDifficulty, 2); - aimDifficulty *= skillMultiplier; + double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true) * skillMultiplier; + aimDifficulty *= Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR), 2); currentStrain += aimDifficulty; @@ -116,12 +114,14 @@ protected override double StrainValueAt(DifficultyHitObject current) public class HighARSpeedComponent : OsuStrainSkill { - private double skillMultiplier => 820; + private double skillMultiplier => 670; protected override double StrainDecayBase => 0.3; private double currentStrain; private double currentRhythm; + private double defaultValueMultiplier => 25; + public HighARSpeedComponent(Mod[] mods) : base(mods) { @@ -131,17 +131,17 @@ public HighARSpeedComponent(Mod[] mods) protected override double StrainValueAt(DifficultyHitObject current) { - OsuDifficultyHitObject currODHO = (OsuDifficultyHitObject)current; + OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current; - currentStrain *= StrainDecay(currODHO.StrainTime); + currentStrain *= StrainDecay(currObj.StrainTime); double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; speedDifficulty *= Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, false), 2); currentStrain += speedDifficulty; - currentRhythm = currODHO.RhythmDifficulty; + currentRhythm = currObj.RhythmDifficulty; double totalStrain = currentStrain * currentRhythm; - return totalStrain; + return totalStrain + defaultValueMultiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current); } } } From d3cdb671c79f51f30b4556bbee6f8888e471c549 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 16 Mar 2024 19:34:17 +0200 Subject: [PATCH 51/96] low AR streams balancing --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 54 ++++- .../Evaluators/ReadingHiddenEvaluator.cs | 219 ------------------ .../Difficulty/Skills/Reading.cs | 5 +- 3 files changed, 50 insertions(+), 228 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHiddenEvaluator.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 30f0df50d4be..c63018ab0aa4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -18,7 +18,7 @@ public static class ReadingEvaluator private const double overlap_multiplier = 0.8; - public static double EvaluateDenstityOf(DifficultyHitObject current) + public static double EvaluateDenstityOf(DifficultyHitObject current, bool applyDistanceNerf = true) { var currObj = (OsuDifficultyHitObject)current; double density = 0; @@ -35,7 +35,7 @@ public static double EvaluateDenstityOf(DifficultyHitObject current) double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - loopDifficulty *= logistic((loopObj.MinimumJumpDistance - 60) / 10); + if (applyDistanceNerf) loopDifficulty *= (logistic((loopObj.MinimumJumpDistance - 60) / 10) + 0.2) / 1.2; // Reduce density bonus for this object if they're too apart in time // Nerf starts on 1500ms and reaches maximum (*=0) on 3000ms @@ -48,6 +48,21 @@ public static double EvaluateDenstityOf(DifficultyHitObject current) continue; } + // Only if next object is slower, representing break from many notes in a row + if (loopObj.StrainTime > prevObj0.StrainTime) + { + // Get rhythm similarity: 1 on same rhythms, 0.5 on 1/4 to 1/2 + double rhythmSimilarity = 1 - getRhythmDifference(loopObj.StrainTime, prevObj0.StrainTime); + + // Make differentiation going from 1/4 to 1/2 and bigger difference + // To 1/3 to 1/2 and smaller difference + rhythmSimilarity = Math.Clamp(rhythmSimilarity, 0.5, 0.75); + rhythmSimilarity = 4 * (rhythmSimilarity - 0.5); + + // Reduce density for this objects if rhythms are different + loopDifficulty *= rhythmSimilarity; + } + density += loopDifficulty; // Angles nerf @@ -133,15 +148,13 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) double pastObjectDifficultyInfluence = EvaluateDenstityOf(current); double screenOverlapDifficulty = CalculateOverlapDifficultyOf(currObj); - double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence)), 2.3); + double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence)), 2.5); - screenOverlapDifficulty = Math.Max(0, screenOverlapDifficulty - 0.5); // make overlap value =1 cost significantly less + screenOverlapDifficulty = Math.Max(0, screenOverlapDifficulty - 0.75); // make overlap value =1 cost significantly less double overlapBonus = overlap_multiplier * screenOverlapDifficulty * difficulty; difficulty += overlapBonus; - //difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty; - return difficulty; } @@ -258,4 +271,33 @@ private static double getTimeNerfFactor(double deltaTime) private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2); private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); } + + public static class ReadingHiddenEvaluator + { + public static double EvaluateDifficultyOf(DifficultyHitObject current) + { + var currObj = (OsuDifficultyHitObject)current; + + double density = ReadingEvaluator.EvaluateDenstityOf(current, false); + + // Consider that density matters only starting from 3rd note on the screen + double densityFactor = Math.Max(0, density - 1) / 4; + + // This is kinda wrong cuz it returns value bigger than preempt + // double timeSpentInvisible = getDurationSpentInvisible(currObj) / 1000 / currObj.ClockRate; + + // The closer timeSpentInvisible is to 0 -> the less difference there are between NM and HD + // So we will reduce base according to this + // It will be 0.354 on AR11 value + double invisibilityFactor = logistic(currObj.Preempt / 120 - 4); + + double hdDifficulty = invisibilityFactor + densityFactor; + + // Scale by inpredictability slightly + hdDifficulty *= 0.95 + 0.15 * ReadingEvaluator.EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 + + return hdDifficulty; + } + private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); + } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHiddenEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHiddenEvaluator.cs deleted file mode 100644 index 6b25e4657ae5..000000000000 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHiddenEvaluator.cs +++ /dev/null @@ -1,219 +0,0 @@ -// 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.Framework.Extensions.ObjectExtensions; -using osu.Game.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; - -namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators -{ - // Class for HD calc. Split because there are a lot of things in HD calc. - public static class ReadingHiddenEvaluator - { - private const double reading_window_size = 3000; - - public static double EvaluateDifficultyOf(DifficultyHitObject current) - { - var currObj = (OsuDifficultyHitObject)current; - - double density = 0; - double densityAnglesNerf = -2; // we have threshold of 2, so 2 or same angles won't be punished - - OsuDifficultyHitObject? prevObj0 = null; - OsuDifficultyHitObject? prevObj1 = null; - OsuDifficultyHitObject? prevObj2 = null; - - double prevConstantAngle = 0; - - foreach (var loopObj in retrievePastVisibleObjects(currObj).Reverse()) - { - double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); - - // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - // For HD: it's not subtracting anything cuz it's multiplied by the aim difficulty anyways. - // loopDifficulty *= logistic((loopObj.MinimumJumpDistance) / 15); - - // Reduce density bonus for this object if they're too apart in time - // Nerf starts on 1500ms and reaches maximum (*=0) on 3000ms - double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime; - loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); - - if (prevObj0.IsNull()) - { - prevObj0 = loopObj; - continue; - } - - // HD-exclusive burst nerf - - // Only if next object is slower, representing break from many notes in a row - if (loopObj.StrainTime > prevObj0.StrainTime) - { - // Get rhythm similarity: 1 on same rhythms, 0.5 on 1/4 to 1/2 - double rhythmSimilarity = 1 - getRhythmDifference(loopObj.StrainTime, prevObj0.StrainTime); - - // Make differentiation going from 1/4 to 1/2 and bigger difference - // To 1/3 to 1/2 and smaller difference - rhythmSimilarity = Math.Clamp(rhythmSimilarity, 0.5, 0.75); - rhythmSimilarity = 4 * (rhythmSimilarity - 0.5); - - // Reduce density for this objects if rhythms are different - loopDifficulty *= rhythmSimilarity; - } - - density += loopDifficulty; - - // Angles nerf - - if (loopObj.Angle.IsNotNull() && prevObj0.Angle.IsNotNull()) - { - double angleDifference = Math.Abs(prevObj0.Angle.Value - loopObj.Angle.Value); - - // Nerf alternating angles case - if (prevObj1.IsNotNull() && prevObj2.IsNotNull() && prevObj1.Angle.IsNotNull() && prevObj2.Angle.IsNotNull()) - { - // Normalized difference - double angleDifference1 = Math.Abs(prevObj1.Angle.Value - loopObj.Angle.Value) / Math.PI; - double angleDifference2 = Math.Abs(prevObj2.Angle.Value - prevObj0.Angle.Value) / Math.PI; - - // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 - double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); - - // Be sure to nerf only same rhythms - double rhythmFactor = 1 - getRhythmDifference(loopObj.StrainTime, prevObj0.StrainTime); // 0 on different rhythm, 1 on same rhythm - rhythmFactor *= 1 - getRhythmDifference(prevObj0.StrainTime, prevObj1.StrainTime); - rhythmFactor *= 1 - getRhythmDifference(prevObj1.StrainTime, prevObj2.StrainTime); - - double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevObj0.Angle.Value) / Math.PI; - - double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); - - prevAngleAdjust *= alternatingFactor; // Nerf if alternating - prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms - prevAngleAdjust *= acuteAngleFactor; - - angleDifference -= prevAngleAdjust; - } - - // Reduce angles nerf if objects are too apart in time - // Angle nerf is starting being reduced from 200ms (150BPM jump) and it reduced to 0 on 2000ms - double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - 200) / (2000 - 200), 0, 1); - - // Current angle nerf. Angle difference less than 15 degrees is considered the same - double currConstantAngle = Math.Cos(4 * Math.Min(Math.PI / 12, angleDifference)) * longIntervalFactor; - - // Apply the nerf only when it's repeated - double currentAngleNerf = Math.Min(currConstantAngle, prevConstantAngle); - - densityAnglesNerf += Math.Min(currentAngleNerf, loopDifficulty); - prevConstantAngle = currConstantAngle; - } - - prevObj2 = prevObj1; - prevObj1 = prevObj0; - prevObj0 = loopObj; - } - - // Apply angles nerf - density -= Math.Max(0, densityAnglesNerf); - - // Consider that density matters only starting from 3rd note on the screen - double densityFactor = Math.Max(0, density - 1) / 4; - - // This is kinda wrong cuz it returns value bigger than preempt - // double timeSpentInvisible = getDurationSpentInvisible(currObj) / 1000 / currObj.ClockRate; - - // The closer timeSpentInvisible is to 0 -> the less difference there are between NM and HD - // So we will reduce base according to this - // It will be 0.354 on AR11 value - double invisibilityFactor = logistic(currObj.Preempt / 120 - 4); - - double hdDifficulty = invisibilityFactor + densityFactor; - - // Scale by inpredictability slightly - hdDifficulty *= 0.95 + 0.15 * ReadingEvaluator.EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 - - return hdDifficulty; - } - - //public static double EvaluateHiddenDifficultyOfOld(DifficultyHitObject current) - //{ - // var currObj = (OsuDifficultyHitObject)current; - - // double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false); - - // double timeSpentInvisible = getDurationSpentInvisible(currObj) / currObj.ClockRate; - - // double density = 1 + Math.Max(0, CalculateDenstityOf(currObj) - 1); - - // double timeDifficultyFactor = density / 1000; - // timeDifficultyFactor *= getConstantAngleNerfFactor(currObj); - - // double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15); - - // double hdDifficulty = visibleObjectFactor * timeSpentInvisible * timeDifficultyFactor + - // (6 + visibleObjectFactor) * aimDifficulty; - - // hdDifficulty *= 0.95 + 0.15 * EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 - - // return hdDifficulty; - //} - - // Returns a list of objects that are visible on screen at - // the point in time at which the current object becomes visible. - private static IEnumerable retrievePastVisibleObjects(OsuDifficultyHitObject current) - { - for (int i = 0; i < current.Index; i++) - { - OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i); - - if (hitObject.IsNull() || - current.StartTime - hitObject.StartTime > reading_window_size || - hitObject.StartTime < current.StartTime - current.Preempt) - break; - - yield return hitObject; - } - } - - //private static double getDurationSpentInvisible(OsuDifficultyHitObject current) - //{ - // var baseObject = (OsuHitObject)current.BaseObject; - - // double fadeOutStartTime = baseObject.StartTime - baseObject.TimePreempt + baseObject.TimeFadeIn; - // double fadeOutDuration = baseObject.TimePreempt * OsuModHidden.FADE_OUT_DURATION_MULTIPLIER; - - // return (fadeOutStartTime + fadeOutDuration) - (baseObject.StartTime - baseObject.TimePreempt); - //} - - //private static List retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) - //{ - // List objects = new List(); - - // for (int i = 0; i < current.Count; i++) - // { - // OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Next(i); - - // if (hitObject.IsNull() || - // (hitObject.StartTime - current.StartTime) > reading_window_size || - // current.StartTime < hitObject.StartTime - hitObject.Preempt) - // break; - - // objects.Add(hitObject); - // } - - // return objects; - //} - - private static double getTimeNerfFactor(double deltaTime) - { - return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); - } - - private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2); - private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); - } -} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index d237ca4eb538..6ad78696e4e8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -16,9 +16,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - private double skillMultiplier => 1.3; + private double skillMultiplier => 1.1; private double aimComponentMultiplier => 0.7; - //private double skillMultiplier => 2; public ReadingLowAR(Mod[] mods) : base(mods) @@ -85,7 +84,7 @@ public override double DifficultyValue() return difficulty; } - public static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 3) * 10.0; + public static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 4) * 6.0; } public class ReadingHidden : OsuStrainSkill From 5e2f3e3918e1f934fef36a8d70e5e61695cee6a7 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 17 Mar 2024 13:28:09 +0200 Subject: [PATCH 52/96] Increased stability Now low AR difficulty won't drop with lowering AR --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index c63018ab0aa4..3df9491395c2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -22,16 +22,19 @@ public static double EvaluateDenstityOf(DifficultyHitObject current, bool applyD { var currObj = (OsuDifficultyHitObject)current; double density = 0; - double densityAnglesNerf = -2; // we have threshold of 2, so 2 or same angles won't be punished + double densityAnglesNerf = -2.5; // we have threshold of 2.5 OsuDifficultyHitObject? prevObj0 = null; OsuDifficultyHitObject? prevObj1 = null; OsuDifficultyHitObject? prevObj2 = null; - double prevConstantAngle = 0; + double prevConstantAngle = 1; foreach (var loopObj in retrievePastVisibleObjects(currObj).Reverse()) { + if (loopObj.Index < 1) + continue; // Don't look on the first object of the map + double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. @@ -43,10 +46,13 @@ public static double EvaluateDenstityOf(DifficultyHitObject current, bool applyD loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); if (prevObj0.IsNull()) - { - prevObj0 = loopObj; - continue; - } + prevObj0 = (OsuDifficultyHitObject)loopObj.Previous(0); + + if (prevObj1.IsNull()) + prevObj1 = (OsuDifficultyHitObject?)loopObj.Previous(1); + + if (prevObj2.IsNull()) + prevObj2 = (OsuDifficultyHitObject?)loopObj.Previous(2); // Only if next object is slower, representing break from many notes in a row if (loopObj.StrainTime > prevObj0.StrainTime) @@ -67,35 +73,42 @@ public static double EvaluateDenstityOf(DifficultyHitObject current, bool applyD // Angles nerf - if (loopObj.Angle.IsNotNull() && prevObj0.Angle.IsNotNull()) + if (loopObj.Angle.IsNotNull() && prevObj0.IsNotNull() && prevObj0.Angle.IsNotNull()) { double angleDifference = Math.Abs(prevObj0.Angle.Value - loopObj.Angle.Value); + // assume worst-case if no angles + double angleDifference1 = 0; + double angleDifference2 = 0; + // Nerf alternating angles case if (prevObj1.IsNotNull() && prevObj2.IsNotNull() && prevObj1.Angle.IsNotNull() && prevObj2.Angle.IsNotNull()) { // Normalized difference - double angleDifference1 = Math.Abs(prevObj1.Angle.Value - loopObj.Angle.Value) / Math.PI; - double angleDifference2 = Math.Abs(prevObj2.Angle.Value - prevObj0.Angle.Value) / Math.PI; + angleDifference1 = Math.Abs(prevObj1.Angle.Value - loopObj.Angle.Value) / Math.PI; + angleDifference2 = Math.Abs(prevObj2.Angle.Value - prevObj0.Angle.Value) / Math.PI; + } + + // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 + double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); - // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 - double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); + // Be sure to nerf only same rhythms + double rhythmFactor = 1 - getRhythmDifference(loopObj.StrainTime, prevObj0.StrainTime); // 0 on different rhythm, 1 on same rhythm - // Be sure to nerf only same rhythms - double rhythmFactor = 1 - getRhythmDifference(loopObj.StrainTime, prevObj0.StrainTime); // 0 on different rhythm, 1 on same rhythm + if (prevObj1.IsNotNull()) rhythmFactor *= 1 - getRhythmDifference(prevObj0.StrainTime, prevObj1.StrainTime); + if (prevObj1.IsNotNull() && prevObj2.IsNotNull()) rhythmFactor *= 1 - getRhythmDifference(prevObj1.StrainTime, prevObj2.StrainTime); - double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevObj0.Angle.Value) / Math.PI; + double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevObj0.Angle.Value) / Math.PI; - double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); + double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); - prevAngleAdjust *= alternatingFactor; // Nerf if alternating - prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms - prevAngleAdjust *= acuteAngleFactor; + prevAngleAdjust *= alternatingFactor; // Nerf if alternating + prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms + prevAngleAdjust *= acuteAngleFactor; - angleDifference -= prevAngleAdjust; - } + angleDifference -= prevAngleAdjust; // Reduce angles nerf if objects are too apart in time // Angle nerf is starting being reduced from 200ms (150BPM jump) and it reduced to 0 on 2000ms @@ -110,6 +123,10 @@ public static double EvaluateDenstityOf(DifficultyHitObject current, bool applyD densityAnglesNerf += Math.Min(currentAngleNerf, loopDifficulty); prevConstantAngle = currConstantAngle; } + else // Assume worst-case if no angles + { + densityAnglesNerf += loopDifficulty; + } prevObj2 = prevObj1; prevObj1 = prevObj0; From ab47d39d0de6592d0fdf6bc422c79e297743e337 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 21 Mar 2024 20:21:52 +0200 Subject: [PATCH 53/96] Balancing 1) overlaps now giving proper amount of pp 2) high AR is nerfed outside of AR11 3) i hope FL won't crash calc anymore 4) flow aim low AR bonus is nerfed --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 32 +++++++++++-------- .../Evaluators/ReadingHighAREvaluator.cs | 2 +- .../Difficulty/OsuDifficultyAttributes.cs | 10 ------ .../Difficulty/OsuDifficultyCalculator.cs | 5 +-- .../Difficulty/Skills/Reading.cs | 8 ++--- 5 files changed, 26 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 3df9491395c2..e14bb3c6b520 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -16,9 +16,9 @@ public static class ReadingEvaluator { private const double reading_window_size = 3000; - private const double overlap_multiplier = 0.8; + private const double overlap_multiplier = 2; - public static double EvaluateDenstityOf(DifficultyHitObject current, bool applyDistanceNerf = true) + public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDistanceNerf = true) { var currObj = (OsuDifficultyHitObject)current; double density = 0; @@ -100,13 +100,13 @@ public static double EvaluateDenstityOf(DifficultyHitObject current, bool applyD if (prevObj1.IsNotNull() && prevObj2.IsNotNull()) rhythmFactor *= 1 - getRhythmDifference(prevObj1.StrainTime, prevObj2.StrainTime); - double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevObj0.Angle.Value) / Math.PI; + // double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevObj0.Angle.Value) / Math.PI; double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); prevAngleAdjust *= alternatingFactor; // Nerf if alternating prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms - prevAngleAdjust *= acuteAngleFactor; + // prevAngleAdjust *= acuteAngleFactor; // no longer needed? angleDifference -= prevAngleAdjust; @@ -138,8 +138,9 @@ public static double EvaluateDenstityOf(DifficultyHitObject current, bool applyD return density; } - public static double CalculateOverlapDifficultyOf(OsuDifficultyHitObject currObj) + public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) { + var currObj = (OsuDifficultyHitObject)current; double screenOverlapDifficulty = 0; foreach (var loopObj in retrievePastVisibleObjects(currObj)) @@ -153,26 +154,29 @@ public static double CalculateOverlapDifficultyOf(OsuDifficultyHitObject currObj screenOverlapDifficulty += lastOverlapness; } - return screenOverlapDifficulty; + return overlap_multiplier * Math.Max(0, screenOverlapDifficulty - 0.7); } public static double EvaluateDifficultyOf(DifficultyHitObject current) { if (current.BaseObject is Spinner || current.Index == 0) return 0; - var currObj = (OsuDifficultyHitObject)current; + double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, EvaluateDensityOf(current))), 2.5); - double pastObjectDifficultyInfluence = EvaluateDenstityOf(current); - double screenOverlapDifficulty = CalculateOverlapDifficultyOf(currObj); + double overlapBonus = EvaluateOverlapDifficultyOf(current) * difficulty; + difficulty += overlapBonus; - double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence)), 2.5); + return difficulty; + } - screenOverlapDifficulty = Math.Max(0, screenOverlapDifficulty - 0.75); // make overlap value =1 cost significantly less + public static double EvaluateAimingDensityFactorOf(DifficultyHitObject current) + { + double difficulty = EvaluateDensityOf(current); - double overlapBonus = overlap_multiplier * screenOverlapDifficulty * difficulty; + double overlapBonus = EvaluateOverlapDifficultyOf(current) * difficulty; difficulty += overlapBonus; - return difficulty; + return Math.Max(0, Math.Pow(difficulty, 1.5) - 1); } // Returns value from 0 to 1, where 0 is very predictable and 1 is very unpredictable @@ -295,7 +299,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) { var currObj = (OsuDifficultyHitObject)current; - double density = ReadingEvaluator.EvaluateDenstityOf(current, false); + double density = ReadingEvaluator.EvaluateDensityOf(current, false); // Consider that density matters only starting from 3rd note on the screen double densityFactor = Math.Max(0, density - 1) / 4; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs index 949860aa765a..461337e65a8f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs @@ -123,7 +123,7 @@ private static double getAlternateWithZeroAnglePunishment(double a1, double a2, // https://www.desmos.com/calculator/hbj7swzlth public static double GetDifficulty(double preempt) { - double value = Math.Pow(3.5, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms + double value = Math.Pow(4, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms) return value; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index a9640e579123..e18c865e7367 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -130,12 +130,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); yield return (ATTRIB_ID_SPEED_NOTE_COUNT, SpeedNoteCount); - yield return (ATTRIB_ID_READING_LOW_AR, ReadingDifficultyLowAR); - yield return (ATTRIB_ID_READING_HIGH_AR, ReadingDifficultyHighAR); - yield return (ATTRIB_ID_READING_SLIDERS, ReadingDifficultySliders); - - if (ShouldSerializeHiddenDifficulty()) - yield return (ATTRIB_ID_READING_HIDDEN, HiddenDifficulty); } public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) @@ -150,10 +144,6 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary val SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT]; - ReadingDifficultyLowAR = values[ATTRIB_ID_READING_LOW_AR]; - ReadingDifficultyHighAR = values[ATTRIB_ID_READING_HIGH_AR]; - ReadingDifficultySliders = values[ATTRIB_ID_READING_SLIDERS]; - HiddenDifficulty = values.GetValueOrDefault(ATTRIB_ID_READING_HIDDEN); FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); DrainRate = onlineInfo.DrainRate; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index b16ad5a21f20..848de57440df 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -173,8 +173,9 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo new HiddenFlashlight(mods), }; - if (mods.Any(h => h is OsuModFlashlight)) - skills.Add(new Flashlight(mods)); + // Why adding flashlight one more time???? + //if (mods.Any(h => h is OsuModFlashlight)) + // skills.Add(new Flashlight(mods)); return skills.ToArray(); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 6ad78696e4e8..7fd88e19b43d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -31,12 +31,12 @@ public ReadingLowAR(Mod[] mods) public override void Process(DifficultyHitObject current) { - double densityFactor = Math.Max(0, Math.Pow(ReadingEvaluator.EvaluateDenstityOf(current), 1.5) - 1); - // double density = Math.Max(0, ReadingEvaluator.EvaluateDenstityOf(current)); + double densityReadingDifficulty = ReadingEvaluator.EvaluateDifficultyOf(current); + double densityAimingFactor = ReadingEvaluator.EvaluateAimingDensityFactorOf(current); + currentDensityAimStrain *= strainDecay(current.DeltaTime); - currentDensityAimStrain += densityFactor * AimEvaluator.EvaluateDifficultyOf(current, false) * aimComponentMultiplier; + currentDensityAimStrain += densityAimingFactor * AimEvaluator.EvaluateDifficultyOf(current, false) * aimComponentMultiplier; - double densityReadingDifficulty = ReadingEvaluator.EvaluateDifficultyOf(current); double totalDensityDifficulty = (currentDensityAimStrain + densityReadingDifficulty) * skillMultiplier; difficulties.Add(totalDensityDifficulty); From 333bfd24909e50cdf2571e4dd165d5ca3ec4e7d8 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 21 Mar 2024 21:35:29 +0200 Subject: [PATCH 54/96] added acc bonus for low AR and slight balancing --- .../Difficulty/OsuDifficultyCalculator.cs | 2 +- .../Difficulty/OsuPerformanceCalculator.cs | 13 ++++++++++--- .../Difficulty/Skills/Flashlight.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs | 4 ++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 848de57440df..b8963b7a45c6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -24,7 +24,7 @@ public class OsuDifficultyCalculator : DifficultyCalculator { public const double DIFFICULTY_MULTIPLIER = 0.067; public const double SUM_POWER = 1.1; - public const double FL_SUM_POWER = 1.6; + public const double FL_SUM_POWER = 1.4; public override int Version => 20220902; public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 8e3ab1a2ba75..94706fcfd715 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -221,9 +221,15 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; - // It's stupid so i removed it, it's better just to increase FL coef - //if (score.Mods.Any(m => m is OsuModFlashlight)) - // accuracyValue *= 1.02; + if (score.Mods.Any(m => m is OsuModFlashlight)) + accuracyValue *= 1.02; + + // Visual indication bonus + double visualIndicationBonus = 1.0 + 0.1 * logistic((8.0 - attributes.ApproachRate) / 6); + + accuracyValue *= visualIndicationBonus; + if (score.Mods.Any(h => h is OsuModHidden)) + accuracyValue *= visualIndicationBonus; return accuracyValue; } @@ -397,5 +403,6 @@ public static double AdjustCognitionPerformance(double cognitionPerformance, dou } 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)); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 9fafeacb9c2b..c30246df15f9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -25,7 +25,7 @@ public Flashlight(Mod[] mods) hasHiddenMod = mods.Any(m => m is OsuModHidden); } - private double skillMultiplier => 0.052; + private double skillMultiplier => 0.054; private double strainDecayBase => 0.15; private double currentStrain; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 7fd88e19b43d..77ab5a68e376 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - private double skillMultiplier => 1.1; + private double skillMultiplier => 1.08; private double aimComponentMultiplier => 0.7; public ReadingLowAR(Mod[] mods) @@ -95,7 +95,7 @@ public ReadingHidden(Mod[] mods) } private double currentStrain; - private double skillMultiplier => 5; + private double skillMultiplier => 4.9; protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); From cead94de77154a1e26121999f085612801f272e8 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 23 Mar 2024 02:35:11 +0200 Subject: [PATCH 55/96] minor balancing update --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 94706fcfd715..a07baa8ad2c1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -225,7 +225,7 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att accuracyValue *= 1.02; // Visual indication bonus - double visualIndicationBonus = 1.0 + 0.1 * logistic((8.0 - attributes.ApproachRate) / 6); + double visualIndicationBonus = 1.0 + 0.1 * logistic(8.0 - attributes.ApproachRate); accuracyValue *= visualIndicationBonus; if (score.Mods.Any(h => h is OsuModHidden)) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 77ab5a68e376..85078f873dec 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -83,8 +83,7 @@ public override double DifficultyValue() return difficulty; } - - public static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 4) * 6.0; + public static double DifficultyToPerformance(double difficulty) => difficulty < 1 ? difficulty * 6.0 : Math.Pow(difficulty, 4) * 6.0; } public class ReadingHidden : OsuStrainSkill From 53b918ecc110e819000f2ffe024a83537d0c9cfe Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 23 Mar 2024 13:35:29 +0200 Subject: [PATCH 56/96] deleted more db-stuff --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 3 --- osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 4 ---- 2 files changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index e18c865e7367..eb12dfce0379 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -161,9 +161,6 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary val [UsedImplicitly] public bool ShouldSerializeFlashlightDifficulty() => Mods.Any(m => m is ModFlashlight); - [UsedImplicitly] - public bool ShouldSerializeHiddenDifficulty() => Mods.Any(m => m is ModHidden); - #endregion } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index b07ade01debe..9690924b1c46 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -26,10 +26,6 @@ public class DifficultyAttributes protected const int ATTRIB_ID_FLASHLIGHT = 17; protected const int ATTRIB_ID_SLIDER_FACTOR = 19; protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21; - protected const int ATTRIB_ID_READING_LOW_AR = 23; - protected const int ATTRIB_ID_READING_HIGH_AR = 25; - protected const int ATTRIB_ID_READING_SLIDERS = 27; - protected const int ATTRIB_ID_READING_HIDDEN = 29; /// /// The mods which were applied to the beatmap. From 529bd8483b7e3ff06b653738c2c914881d117272 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 23 Mar 2024 13:44:47 +0200 Subject: [PATCH 57/96] more clean-up --- .../Difficulty/OsuDifficultyAttributes.cs | 3 +-- .../Preprocessing/OsuDifficultyHitObject.cs | 15 --------------- .../Difficulty/Skills/ReadingHighAR.cs | 5 ----- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index eb12dfce0379..4ee92c7b8f72 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -141,11 +141,10 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary val OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY]; ApproachRate = values[ATTRIB_ID_APPROACH_RATE]; StarRating = values[ATTRIB_ID_DIFFICULTY]; + FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT]; - FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); - DrainRate = onlineInfo.DrainRate; HitCircleCount = onlineInfo.CircleCount; SliderCount = onlineInfo.SliderCount; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 9ae92767da54..84b7a7a30156 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -150,9 +150,6 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje OsuDifficultyHitObject prevObject = this; - bool log = false; - if (log) Console.WriteLine($"Checking for object {hitObject.StartTime}"); - foreach (var loopObj in retrieveCurrentVisibleObjects(this)) { double currentOverlapness = calculateOverlapness(this, loopObj); // overlapness with this object @@ -162,12 +159,8 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje if (loopObj.Angle != null) angleFactor += (-Math.Cos((double)loopObj.Angle) + 1) / 2; // =2 for wide angles, =1 for acute angles instantOverlapness = Math.Min(1, instantOverlapness * angleFactor); // wide angles are more predictable - if (log) Console.WriteLine($"Base overlapness - {currentOverlapness}"); - currentOverlapness *= (1 - instantOverlapness) * 2; // wide angles will have close-to-zero buff - if (log) Console.WriteLine($"Adjusted overlapness - {currentOverlapness}"); - if (currentOverlapness > 0) { double difference = Math.Min(timeWithoutOverlap, prevTimeWithoutOverlap) / Math.Max(timeWithoutOverlap, prevTimeWithoutOverlap); @@ -175,8 +168,6 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje currentOverlapness *= differenceActuation(difference); - if (log) Console.WriteLine($"Overlapness [{prevTimeWithoutOverlap} -> {timeWithoutOverlap}], difference {difference}, actuation {differenceActuation(difference)}, result {currentOverlapness}"); - prevTimeWithoutOverlap = timeWithoutOverlap; timeWithoutOverlap = 0; } @@ -184,18 +175,12 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje else { timeWithoutOverlap += prevObject.DeltaTime; - - if (log) Console.WriteLine($"No overlapness, adding {prevObject.DeltaTime}, result {timeWithoutOverlap}"); } totalOverlapnessDifficulty += currentOverlapness; OverlapObjects.Add(new OverlapObject(loopObj, totalOverlapnessDifficulty)); prevObject = loopObj; - - if (log) Console.WriteLine($"Added object with difficulty {totalOverlapnessDifficulty}\n"); } - - if (log) Console.WriteLine("\n"); } private static double differenceActuation(double difference) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index 531a18a58169..130df2d6eb95 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -19,12 +19,9 @@ public ReadingHighAR(Mod[] mods) { aimComponent = new HighARAimComponent(mods); speedComponent = new HighARSpeedComponent(mods); - - aimComponentNoAdjust = new HighARAimComponent(mods, false); } private HighARAimComponent aimComponent; - private HighARAimComponent aimComponentNoAdjust; private HighARSpeedComponent speedComponent; private readonly List difficulties = new List(); @@ -35,8 +32,6 @@ public override void Process(DifficultyHitObject current) aimComponent.Process(current); speedComponent.Process(current); - aimComponentNoAdjust.Process(current); - if (current.BaseObject is not Spinner) objectsCount++; From 4d2cb5788715ef16d59a815ce0101d9070d481a0 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 23 Mar 2024 15:08:03 +0200 Subject: [PATCH 58/96] fixed very stupid bug --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index e14bb3c6b520..32abb1c21f01 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -148,7 +148,7 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) double lastOverlapness = 0; foreach (var overlapObj in loopObj.OverlapObjects) { - if (overlapObj.HitObject.StartTime + overlapObj.HitObject.Preempt > currObj.StartTime) break; + if (overlapObj.HitObject.StartTime + overlapObj.HitObject.Preempt >= currObj.StartTime) break; lastOverlapness = overlapObj.Overlapness; } screenOverlapDifficulty += lastOverlapness; From 6b1320e1cd011483de1136a83e2a441abbd5497e Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 23 Mar 2024 15:21:19 +0200 Subject: [PATCH 59/96] increased stability of the overlaps --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 84b7a7a30156..0a126edee88e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -160,6 +160,7 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje instantOverlapness = Math.Min(1, instantOverlapness * angleFactor); // wide angles are more predictable currentOverlapness *= (1 - instantOverlapness) * 2; // wide angles will have close-to-zero buff + currentOverlapness *= loopObj.OpacityAt(BaseObject.StartTime, false); if (currentOverlapness > 0) { From c016ed0a8aceabd7ae8eaa323cfa1b7d123b905a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 23 Mar 2024 15:25:29 +0200 Subject: [PATCH 60/96] Update OsuDifficultyHitObject.cs --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 0a126edee88e..17b4b4749275 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -160,7 +160,7 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje instantOverlapness = Math.Min(1, instantOverlapness * angleFactor); // wide angles are more predictable currentOverlapness *= (1 - instantOverlapness) * 2; // wide angles will have close-to-zero buff - currentOverlapness *= loopObj.OpacityAt(BaseObject.StartTime, false); + currentOverlapness *= OpacityAt(loopObj.BaseObject.StartTime, false); if (currentOverlapness > 0) { From 403dc5b8b067460fb3c2aec11bf9be802ec72564 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 24 Mar 2024 00:21:34 +0200 Subject: [PATCH 61/96] Walk This Way bandaid --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 32abb1c21f01..bea94e42cddc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -28,7 +28,7 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi OsuDifficultyHitObject? prevObj1 = null; OsuDifficultyHitObject? prevObj2 = null; - double prevConstantAngle = 1; + double prevAngleNerf = 1; foreach (var loopObj in retrievePastVisibleObjects(currObj).Reverse()) { @@ -77,6 +77,12 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi { double angleDifference = Math.Abs(prevObj0.Angle.Value - loopObj.Angle.Value); + // Assume that very low spacing difference means that angles don't matter + if (prevObj0.LazyJumpDistance < OsuDifficultyHitObject.NORMALISED_RADIUS) + angleDifference *= Math.Pow(prevObj0.LazyJumpDistance / OsuDifficultyHitObject.NORMALISED_RADIUS, 2); + if (loopObj.LazyJumpDistance < OsuDifficultyHitObject.NORMALISED_RADIUS) + angleDifference *= Math.Pow(loopObj.LazyJumpDistance / OsuDifficultyHitObject.NORMALISED_RADIUS, 2); + // assume worst-case if no angles double angleDifference1 = 0; double angleDifference2 = 0; @@ -115,13 +121,13 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - 200) / (2000 - 200), 0, 1); // Current angle nerf. Angle difference less than 15 degrees is considered the same - double currConstantAngle = Math.Cos(4 * Math.Min(Math.PI / 12, angleDifference)) * longIntervalFactor; + double currAngleNerf = Math.Cos(4 * Math.Min(Math.PI / 12, angleDifference)) * longIntervalFactor; // Apply the nerf only when it's repeated - double currentAngleNerf = Math.Min(currConstantAngle, prevConstantAngle); + double angleNerf = Math.Min(currAngleNerf, prevAngleNerf); - densityAnglesNerf += Math.Min(currentAngleNerf, loopDifficulty); - prevConstantAngle = currConstantAngle; + densityAnglesNerf += Math.Min(angleNerf, loopDifficulty); + prevAngleNerf = currAngleNerf; } else // Assume worst-case if no angles { From 4051413c92af3ea4cb1cbc07de0e9b7829927155 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 24 Mar 2024 19:34:06 +0200 Subject: [PATCH 62/96] rubik's cube bandaid --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index bea94e42cddc..9dded267d01e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -120,12 +120,34 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi // Angle nerf is starting being reduced from 200ms (150BPM jump) and it reduced to 0 on 2000ms double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - 200) / (2000 - 200), 0, 1); - // Current angle nerf. Angle difference less than 15 degrees is considered the same - double currAngleNerf = Math.Cos(4 * Math.Min(Math.PI / 12, angleDifference)) * longIntervalFactor; + // Bandaid to fix Rubik's Cube +EZ + double wideness = 0; + if (loopObj.Angle.Value > Math.PI * 0.5) + { + // Goes from 0 to 1 as angle increasing from 90 degrees to 180 + wideness = (loopObj.Angle.Value / Math.PI - 0.5) * 2; + + // Transform into quadratic scaling + wideness = 1 - Math.Pow(1 - wideness, 2); + } + + // Angle difference will be considered as 2 times lower if angle is wide + angleDifference /= 1 + wideness; + + // Current angle nerf. Angle difference more than 15 degrees gets no penalty + double adjustedAngleDifference = Math.Min(Math.PI / 12, angleDifference); + + // WARNING - this thing always gives at least 0.5 angle nerf, this is a bug, but removing it completely ruins everything + // Theoretically - this issue is fixable by changing multipliers everywhere, + // but this is not needed because this bug have no drawbacks outside of algorithm not working as intended + double currAngleNerf = Math.Cos(Math.Min(Math.PI / 2, 4 * adjustedAngleDifference)); // Apply the nerf only when it's repeated double angleNerf = Math.Min(currAngleNerf, prevAngleNerf); + // But only for sharp angles + angleNerf += wideness * (currAngleNerf - angleNerf); + densityAnglesNerf += Math.Min(angleNerf, loopDifficulty); prevAngleNerf = currAngleNerf; } From 98873e24012b34849f65a4d651cd77d59b7620fc Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 25 Mar 2024 01:02:38 +0200 Subject: [PATCH 63/96] fixed high AR (i hope) --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 40 +++++- .../Evaluators/ReadingHighAREvaluator.cs | 135 ------------------ .../Difficulty/OsuPerformanceCalculator.cs | 6 +- .../Difficulty/Skills/Reading.cs | 4 +- .../Difficulty/Skills/ReadingHighAR.cs | 7 +- 5 files changed, 44 insertions(+), 148 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 9dded267d01e..fa042f23073e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -211,9 +211,9 @@ public static double EvaluateAimingDensityFactorOf(DifficultyHitObject current) public static double EvaluateInpredictabilityOf(DifficultyHitObject current) { // make the sum equal to 1 - const double velocity_change_part = 0.25; - const double angle_change_part = 0.45; - const double rhythm_change_part = 0.3; + const double velocity_change_part = 0.8; + const double angle_change_part = 0.1; + const double rhythm_change_part = 0.1; if (current.BaseObject is Spinner || current.Index == 0 || current.Previous(0).BaseObject is Spinner) return 0; @@ -349,4 +349,38 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) } private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); } + + public static class ReadingHighAREvaluator + { + public static double EvaluateDifficultyOf(DifficultyHitObject current, bool applyAdjust = false) + { + var currObj = (OsuDifficultyHitObject)current; + + double result = GetDifficulty(currObj.Preempt); + + if (applyAdjust) + { + double inpredictability = ReadingEvaluator.EvaluateInpredictabilityOf(current); + + // follow lines make high AR easier, so apply nerf if object isn't new combo + inpredictability *= 1 + 0.1 * (800 - currObj.FollowLineTime) / 800; + + result *= 0.98 + 0.6 * inpredictability; + } + + return result; + } + + // High AR curve + // https://www.desmos.com/calculator/srzbeumngi + public static double GetDifficulty(double preempt) + { + // Get preempt in seconds + preempt /= 1000; + if (preempt < 0.375) // We have stop in the point of AR10.5, the value here = 0.396875, derivative = -10.5833, + return 0.63 * Math.Pow(8 - 20 * preempt, 2.0 / 3); // This function is matching live high AR bonus + else + return Math.Exp(9.07583 - 80.0 * preempt / 3); + } + } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs deleted file mode 100644 index 461337e65a8f..000000000000 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs +++ /dev/null @@ -1,135 +0,0 @@ -// 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 osu.Framework.Extensions.ObjectExtensions; -using osu.Game.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators -{ - // Main class with some util functions - public static class ReadingHighAREvaluator - { - public static double EvaluateDifficultyOf(DifficultyHitObject current, bool applyAdjust = false) - { - var currObj = (OsuDifficultyHitObject)current; - - double result = GetDifficulty(currObj.Preempt); - - if (applyAdjust) - { - double inpredictability = ReadingEvaluator.EvaluateInpredictabilityOf(current); - - // follow lines make high AR easier, so apply nerf if object isn't new combo - inpredictability *= 1 + 0.1 * (800 - currObj.FollowLineTime) / 800; - - result *= 0.9 + 1 * inpredictability; - result *= 1.05 - 0.4 * EvaluateFieryAnglePunishmentOf(current); - } - - return result; - } - - // Explicitely nerfs edgecased fiery-type jumps for high AR. The difference from Inpredictability is that this is not used in HD calc - public static double EvaluateFieryAnglePunishmentOf(DifficultyHitObject current) - { - if (current.Index <= 2) - return 0; - - var currObj = (OsuDifficultyHitObject)current; - var lastObj0 = (OsuDifficultyHitObject)current.Previous(0); - var lastObj1 = (OsuDifficultyHitObject)current.Previous(1); - var lastObj2 = (OsuDifficultyHitObject)current.Previous(2); - - if (currObj.Angle.IsNull() || lastObj0.Angle.IsNull() || lastObj1.Angle.IsNull() || lastObj2.Angle.IsNull()) - return 0; - - // Punishment will be reduced if velocity is changing - double velocityChangeFactor = getVelocityChangeFactor(currObj, lastObj0); - velocityChangeFactor = 1 - Math.Pow(velocityChangeFactor, 2); - - double a1 = currObj.Angle.Value / Math.PI; - double a2 = lastObj0.Angle.Value / Math.PI; - double a3 = lastObj1.Angle.Value / Math.PI; - double a4 = lastObj2.Angle.Value / Math.PI; - - // - 4 same sharp angles in a row: (0.3 0.3 0.3 0.3) -> max punishment - - // Normalized difference - double angleDifference1 = Math.Abs(a1 - a2); - double angleDifference2 = Math.Abs(a1 - a3); - double angleDifference3 = Math.Abs(a1 - a4); - - // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 - double sameAnglePunishment = Math.Pow((1 - angleDifference1) * (1 - angleDifference2) * (1 - angleDifference3), 3); - - // Starting from 60 degrees - reduce same angle punishment - double angleSharpnessFactor = Math.Max(0, a1 - 1.0 / 3); - angleSharpnessFactor = 1 - angleSharpnessFactor; - - sameAnglePunishment *= angleSharpnessFactor; - sameAnglePunishment *= velocityChangeFactor; - sameAnglePunishment *= 0.75; - - // - Alternating angles with 0: (0.3 0 0.3 0) or (0 0.3 0 0.3) -> max punishment, (0.3 0 0.1 0) -> some punishment - - double alternateWithZeroAnglePunishment = Math.Max( - getAlternateWithZeroAnglePunishment(a1, a2, a3, a4), - getAlternateWithZeroAnglePunishment(a2, a1, a4, a3)); - alternateWithZeroAnglePunishment *= velocityChangeFactor; - - return Math.Min(1, sameAnglePunishment + alternateWithZeroAnglePunishment); - } - - private static double getVelocityChangeFactor(OsuDifficultyHitObject osuCurrObj, OsuDifficultyHitObject osuLastObj) - { - double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; - double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime; - - double velocityChangeFactor = 0; - - // https://www.desmos.com/calculator/kqxmqc8pkg - if (currVelocity > 0 || prevVelocity > 0) - { - double velocityChange = Math.Max(0, - Math.Min( - Math.Abs(prevVelocity - currVelocity) - 0.5 * Math.Min(currVelocity, prevVelocity), - Math.Max(((OsuHitObject)osuCurrObj.BaseObject).Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Min(currVelocity, prevVelocity)) - )); // Stealed from xexxar - velocityChangeFactor = velocityChange / Math.Max(currVelocity, prevVelocity); // maxiumum is 0.4 - velocityChangeFactor /= 0.4; - } - - return velocityChangeFactor; - } - - private static double getAlternateWithZeroAnglePunishment(double a1, double a2, double a3, double a4) - { - // We assume that a1 and a3 are 0 - double zeroFactor = Math.Pow((1 - a1) * (1 - a3), 8); - zeroFactor *= Math.Pow(1 - Math.Abs(a1 - a3), 2); - - double angleSimilarityFactor = 1 - Math.Abs(a2 - a4); - double angleSharpnessFactor = Math.Min(1 - Math.Max(0, a2 - 1.0 / 3), 1 - Math.Max(0, a4 - 1.0 / 3)); - - return zeroFactor * angleSimilarityFactor * angleSharpnessFactor; - } - - // High AR curve - // https://www.desmos.com/calculator/hbj7swzlth - public static double GetDifficulty(double preempt) - { - double value = Math.Pow(4, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms - value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms) - return value; - } - - // We are using mutiply and divide instead of add and subtract, so values won't be negative - // https://www.desmos.com/calculator/fv5xerwpd2 - 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); - } -} diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index a07baa8ad2c1..3840988349e5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -83,7 +83,6 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double lowARValue = computeReadingLowARValue(score, osuAttributes); double readingHDValue = computeReadingHiddenValue(score, osuAttributes); - double readingSlidersValue = 0; double highARValue = computeReadingHighARValue(score, osuAttributes); @@ -95,8 +94,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double flPower = OsuDifficultyCalculator.FL_SUM_POWER; double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, flPower) + Math.Pow(readingARValue, flPower), 1.0 / flPower); - double readingNonARValue = readingHDValue + readingSlidersValue; - double cognitionValue = Math.Pow(Math.Pow(flashlightARValue, power) + Math.Pow(readingNonARValue, power), 1.0 / power); + double cognitionValue = flashlightARValue + readingHDValue; cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialHiddenFlashlightValue); double accuracyValue = computeAccuracyValue(score, osuAttributes); @@ -104,9 +102,9 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double totalValue = Math.Pow( Math.Pow(mechanicalValue, power) + - Math.Pow(cognitionValue, power) + Math.Pow(accuracyValue, power), 1.0 / power ) * multiplier; + totalValue += cognitionValue * multiplier; return new OsuPerformanceAttributes { diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 85078f873dec..9482b0a41387 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - private double skillMultiplier => 1.08; + private double skillMultiplier => 1.04; private double aimComponentMultiplier => 0.7; public ReadingLowAR(Mod[] mods) @@ -94,7 +94,7 @@ public ReadingHidden(Mod[] mods) } private double currentStrain; - private double skillMultiplier => 4.9; + private double skillMultiplier => 4.8; protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index 130df2d6eb95..40aa25d03bb6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -73,8 +73,7 @@ public override double DifficultyValue() double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance); double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); - // have the same value as difficultyValue at 500pp point - return 75 * Math.Sqrt(difficultyValue); + return 54 * Math.Sqrt(difficultyValue); } } @@ -89,7 +88,7 @@ public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) private bool adjustHighAR; private double currentStrain; - private double skillMultiplier => 17; + private double skillMultiplier => 6.85; private double defaultValueMultiplier => 25; protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); @@ -109,7 +108,7 @@ protected override double StrainValueAt(DifficultyHitObject current) public class HighARSpeedComponent : OsuStrainSkill { - private double skillMultiplier => 670; + private double skillMultiplier => 400; protected override double StrainDecayBase => 0.3; private double currentStrain; From b4fadc373ee037df474db775402612aeaf5094a9 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 25 Mar 2024 01:07:14 +0200 Subject: [PATCH 64/96] change SR scalig --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 4 ++-- .../Difficulty/OsuDifficultyCalculator.cs | 19 ++++--------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index fa042f23073e..7043f60b2fef 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -127,8 +127,8 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi // Goes from 0 to 1 as angle increasing from 90 degrees to 180 wideness = (loopObj.Angle.Value / Math.PI - 0.5) * 2; - // Transform into quadratic scaling - wideness = 1 - Math.Pow(1 - wideness, 2); + // Transform into cubic scaling + wideness = 1 - Math.Pow(1 - wideness, 3); } // Angle difference will be considered as 2 times lower if angle is wide diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index b8963b7a45c6..481834d95b51 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -45,7 +45,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double readingLowARRating = Math.Sqrt(skills[4].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double readingHighARRating = Math.Sqrt(skills[5].DifficultyValue()) * DIFFICULTY_MULTIPLIER; - double readingSlidersRating = 0; double hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double hiddenFlashlightRating = Math.Sqrt(skills[7].DifficultyValue()) * DIFFICULTY_MULTIPLIER; @@ -74,7 +73,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating); double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating); - double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SUM_POWER) + Math.Pow(baseReadingHighARPerformance, SUM_POWER), 1.0 / SUM_POWER); + double baseReadingARPerformance = baseReadingLowARPerformance + baseReadingHighARPerformance; double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FL_SUM_POWER) + Math.Pow(baseReadingARPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER); @@ -82,9 +81,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat if (mods.Any(h => h is OsuModHidden)) baseReadingHiddenPerformance = ReadingHidden.DifficultyToPerformance(hiddenRating); - double baseReadingSliderPerformance = 0; - double baseReadingNonARPerformance = baseReadingHiddenPerformance + baseReadingSliderPerformance; - double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; @@ -94,20 +90,14 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat int sliderCount = beatmap.HitObjects.Count(h => h is Slider); int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner); - // Limit cognition by full memorisation difficulty - double cognitionPerformance = Math.Pow(Math.Pow(baseFlashlightARPerformance, SUM_POWER) + Math.Pow(baseReadingNonARPerformance, SUM_POWER), 1.0 / SUM_POWER); + double cognitionPerformance = baseFlashlightARPerformance + baseReadingHiddenPerformance; double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SUM_POWER) + Math.Pow(baseSpeedPerformance, SUM_POWER), 1.0 / SUM_POWER); + // Limit cognition by full memorisation difficulty double maxHiddenFlashlightPerformance = OsuPerformanceCalculator.ComputePerfectFlashlightValue(hiddenFlashlightRating, hitCirclesCount + sliderCount); - cognitionPerformance = OsuPerformanceCalculator.AdjustCognitionPerformance(cognitionPerformance, mechanicalPerformance, maxHiddenFlashlightPerformance); - double basePerformance = - Math.Pow( - Math.Pow(mechanicalPerformance, SUM_POWER) + - Math.Pow(cognitionPerformance, SUM_POWER) - , 1.0 / SUM_POWER - ); + double basePerformance = mechanicalPerformance + cognitionPerformance; double starRating = basePerformance > 0.00001 ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) @@ -127,7 +117,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat SpeedNoteCount = speedNotes, ReadingDifficultyLowAR = readingLowARRating, ReadingDifficultyHighAR = readingHighARRating, - ReadingDifficultySliders = readingSlidersRating, HiddenDifficulty = hiddenRating, FlashlightDifficulty = flashlightRating, HiddenFlashlightDifficulty = hiddenFlashlightRating, From d96eeeb27ce21769f54d4653fae351b401a6b375 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 25 Mar 2024 19:05:53 +0200 Subject: [PATCH 65/96] high AR changes --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 13 ++++++++---- .../Difficulty/OsuDifficultyCalculator.cs | 4 ++-- .../Difficulty/OsuPerformanceCalculator.cs | 21 +++++++------------ .../Difficulty/Skills/ReadingHighAR.cs | 10 ++++----- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 7043f60b2fef..09191495a93e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -338,12 +338,12 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) // The closer timeSpentInvisible is to 0 -> the less difference there are between NM and HD // So we will reduce base according to this // It will be 0.354 on AR11 value - double invisibilityFactor = logistic(currObj.Preempt / 120 - 4); + double invisibilityFactor = logistic(currObj.Preempt / 160 - 4); double hdDifficulty = invisibilityFactor + densityFactor; // Scale by inpredictability slightly - hdDifficulty *= 0.95 + 0.15 * ReadingEvaluator.EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 + hdDifficulty *= 0.96 + 0.1 * ReadingEvaluator.EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 return hdDifficulty; } @@ -377,10 +377,15 @@ public static double GetDifficulty(double preempt) { // Get preempt in seconds preempt /= 1000; + double value; + if (preempt < 0.375) // We have stop in the point of AR10.5, the value here = 0.396875, derivative = -10.5833, - return 0.63 * Math.Pow(8 - 20 * preempt, 2.0 / 3); // This function is matching live high AR bonus + value = 0.63 * Math.Pow(8 - 20 * preempt, 2.0 / 3); // This function is matching live high AR bonus else - return Math.Exp(9.07583 - 80.0 * preempt / 3); + value = Math.Exp(9.07583 - 80.0 * preempt / 3); + + // EDIT: looks like AR11 getting a bit overnerfed in comparison to other ARs, so i will increase the difference + return Math.Pow(value, 1.4); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 481834d95b51..10e6b4e5a24e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -72,8 +72,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating); double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating); - double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating); - double baseReadingARPerformance = baseReadingLowARPerformance + baseReadingHighARPerformance; + double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating) * 0.5; // WARNING, this is purely visual change to reduce SR inflation on high-end + double baseReadingARPerformance = Math.Max(baseReadingLowARPerformance, baseReadingHighARPerformance); double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FL_SUM_POWER) + Math.Pow(baseReadingARPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 3840988349e5..16d15a7ea711 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -86,9 +86,8 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double highARValue = computeReadingHighARValue(score, osuAttributes); - double readingARValue = Math.Pow( - Math.Pow(lowARValue, power) + - Math.Pow(highARValue, power), 1.0 / power); + // Take only max to reduce pp inflation + double readingARValue = Math.Max(lowARValue, highARValue); // Reduce AR reading bonus if FL is present double flPower = OsuDifficultyCalculator.FL_SUM_POWER; @@ -291,10 +290,6 @@ private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttribute { double highARValue = OsuStrainSkill.DifficultyToPerformance(attributes.ReadingDifficultyHighAR); - // Second half of length bonus, to match mechanical skills SR scaling - double lengthBonus = CalculateDefaultLengthBonus(totalHits); - highARValue *= lengthBonus; - // 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); @@ -351,20 +346,20 @@ private double computeReadingHiddenValue(ScoreInfo score, OsuDifficultyAttribute return 0.0; double rawReading = attributes.HiddenDifficulty; - double readingValue = ReadingHidden.DifficultyToPerformance(attributes.HiddenDifficulty); + double hiddenValue = ReadingHidden.DifficultyToPerformance(attributes.HiddenDifficulty); // 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)); + hiddenValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); - readingValue *= getComboScalingFactor(attributes); + hiddenValue *= getComboScalingFactor(attributes); // Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework. - readingValue *= accuracy * accuracy; + hiddenValue *= accuracy * accuracy; // It is important to also consider accuracy difficulty when doing that. - readingValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; + hiddenValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; - return readingValue; + return hiddenValue; } private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index 40aa25d03bb6..6832993d8c87 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -66,14 +66,14 @@ public override double DifficultyValue() double power = OsuDifficultyCalculator.SUM_POWER; double totalPerformance = Math.Pow(Math.Pow(aimPerformance, power) + Math.Pow(speedPerformance, power), 1.0 / power); - // First half of length bonus is in SR to not inflate Star Rating short AR11 maps + // Length bonus is in SR to not inflate Star Rating short AR11 maps double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount); - totalPerformance *= lengthBonus; + totalPerformance *= lengthBonus * lengthBonus; double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance); double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); - return 54 * Math.Sqrt(difficultyValue); + return 53.2 * Math.Sqrt(difficultyValue); } } @@ -88,7 +88,7 @@ public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) private bool adjustHighAR; private double currentStrain; - private double skillMultiplier => 6.85; + private double skillMultiplier => 8.9; private double defaultValueMultiplier => 25; protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); @@ -108,7 +108,7 @@ protected override double StrainValueAt(DifficultyHitObject current) public class HighARSpeedComponent : OsuStrainSkill { - private double skillMultiplier => 400; + private double skillMultiplier => 520; protected override double StrainDecayBase => 0.3; private double currentStrain; From 6ec5bb5dca8a09bd57086fa1ae099184df5c05a6 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 26 Mar 2024 02:25:40 +0200 Subject: [PATCH 66/96] Fixed the overlap bug --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 7 +- .../Preprocessing/OsuDifficultyHitObject.cs | 68 +++++++++++++------ 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 09191495a93e..fde3ebe7e95c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -16,7 +16,7 @@ public static class ReadingEvaluator { private const double reading_window_size = 3000; - private const double overlap_multiplier = 2; + private const double overlap_multiplier = 3.5; public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDistanceNerf = true) { @@ -180,6 +180,9 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) lastOverlapness = overlapObj.Overlapness; } screenOverlapDifficulty += lastOverlapness; + + // This is a correct way to do this (paired with changing >= to <=), but somehow it get's more broken + //screenOverlapDifficulty = Math.Max(screenOverlapDifficulty, lastOverlapness); } return overlap_multiplier * Math.Max(0, screenOverlapDifficulty - 0.7); @@ -202,7 +205,7 @@ public static double EvaluateAimingDensityFactorOf(DifficultyHitObject current) double difficulty = EvaluateDensityOf(current); double overlapBonus = EvaluateOverlapDifficultyOf(current) * difficulty; - difficulty += overlapBonus; + difficulty += overlapBonus * 0.1; // Overlaps should affect aiming part much less return Math.Max(0, Math.Pow(difficulty, 1.5) - 1); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 17b4b4749275..47efc28cbb35 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -144,38 +144,63 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje OverlapObjects = new List(); double totalOverlapnessDifficulty = 0; - - double prevTimeWithoutOverlap = 0; - double timeWithoutOverlap = 0; + double currentTime = DeltaTime; + List historicTimes = new List(); OsuDifficultyHitObject prevObject = this; foreach (var loopObj in retrieveCurrentVisibleObjects(this)) { - double currentOverlapness = calculateOverlapness(this, loopObj); // overlapness with this object - double instantOverlapness = 0.5 + calculateOverlapness(prevObject, loopObj); // overlapness between current and prev to make streams have 0 buff + // Overlapness with this object + double currentOverlapness = calculateOverlapness(this, loopObj); + + // Overlapness between current and prev to make streams have 0 buff + double instantOverlapness = 0.5 + calculateOverlapness(prevObject, loopObj); + // Nerf overlaps on wide angles double angleFactor = 1; - if (loopObj.Angle != null) angleFactor += (-Math.Cos((double)loopObj.Angle) + 1) / 2; // =2 for wide angles, =1 for acute angles + if (prevObject.Angle != null) angleFactor += (-Math.Cos((double)prevObject.Angle) + 1) / 2; // =2 for wide angles, =1 for acute angles instantOverlapness = Math.Min(1, instantOverlapness * angleFactor); // wide angles are more predictable currentOverlapness *= (1 - instantOverlapness) * 2; // wide angles will have close-to-zero buff currentOverlapness *= OpacityAt(loopObj.BaseObject.StartTime, false); + // Control overlap repetitivness if (currentOverlapness > 0) { - double difference = Math.Min(timeWithoutOverlap, prevTimeWithoutOverlap) / Math.Max(timeWithoutOverlap, prevTimeWithoutOverlap); - if (Math.Max(timeWithoutOverlap, prevTimeWithoutOverlap) == 0) difference = 0; - - currentOverlapness *= differenceActuation(difference); - - prevTimeWithoutOverlap = timeWithoutOverlap; - timeWithoutOverlap = 0; + double currentMinOverlapness = currentOverlapness; + double cumulativeTimeWithCurrent = currentTime; + + // For every cumulative time with current + for (int i = historicTimes.Count - 1; i >= 0; i--) + { + double cumulativeTimeWithoutCurrent = 0; + + // Get every possible cumulative time without current + for (int j = i; j >= 0; j--) + { + cumulativeTimeWithoutCurrent += historicTimes[j]; + + // Check how similar cumulative times are + currentMinOverlapness = Math.Min(currentMinOverlapness, currentOverlapness * getSimilarity(cumulativeTimeWithCurrent, cumulativeTimeWithoutCurrent)); + + // Check how similar current time with cumulative time + currentMinOverlapness = Math.Min(currentMinOverlapness, currentOverlapness * getSimilarity(currentTime, cumulativeTimeWithoutCurrent)); + + // Starting from this point - we will never have better match, so stop searching + if (cumulativeTimeWithoutCurrent >= cumulativeTimeWithCurrent) + break; + } + cumulativeTimeWithCurrent += historicTimes[i]; + } + + currentOverlapness = currentMinOverlapness; + historicTimes.Add(currentTime); + currentTime = prevObject.DeltaTime; } - else { - timeWithoutOverlap += prevObject.DeltaTime; + currentTime += prevObject.DeltaTime; } totalOverlapnessDifficulty += currentOverlapness; @@ -184,17 +209,20 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje } } - private static double differenceActuation(double difference) + private static double getSimilarity(double timeA, double timeB) { - if (difference < 0.75) return 1.0; - if (difference > 0.9) return 0.0; + double similarity = Math.Min(timeA, timeB) / Math.Max(timeA, timeB); + if (Math.Max(timeA, timeB) == 0) similarity = 1; + + if (similarity < 0.75) return 1.0; + if (similarity > 0.9) return 0.0; - return (Math.Cos((difference - 0.75) * Math.PI / 0.15) + 1) / 2; // drops from 1 to 0 as difference increase from 0.75 to 0.9 + return (Math.Cos((similarity - 0.75) * Math.PI / 0.15) + 1) / 2; // drops from 1 to 0 as similarity increase from 0.75 to 0.9 } private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDifficultyHitObject odho2) { - const double area_coef = 0.8; + const double area_coef = 0.85; OsuHitObject o1 = odho1.BaseObject, o2 = odho2.BaseObject; From c4af2bbf69f81f7349d683011dc286a2333a0b07 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 26 Mar 2024 02:55:06 +0200 Subject: [PATCH 67/96] optimisation --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 8 +++----- .../Preprocessing/OsuDifficultyHitObject.cs | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index fde3ebe7e95c..0987d3630a3a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -192,7 +192,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) if (current.BaseObject is Spinner || current.Index == 0) return 0; - double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, EvaluateDensityOf(current))), 2.5); + double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, ((OsuDifficultyHitObject)current).Density)), 2.5); double overlapBonus = EvaluateOverlapDifficultyOf(current) * difficulty; difficulty += overlapBonus; @@ -202,7 +202,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) public static double EvaluateAimingDensityFactorOf(DifficultyHitObject current) { - double difficulty = EvaluateDensityOf(current); + double difficulty = ((OsuDifficultyHitObject)current).Density; double overlapBonus = EvaluateOverlapDifficultyOf(current) * difficulty; difficulty += overlapBonus * 0.1; // Overlaps should affect aiming part much less @@ -330,10 +330,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) { var currObj = (OsuDifficultyHitObject)current; - double density = ReadingEvaluator.EvaluateDensityOf(current, false); - // Consider that density matters only starting from 3rd note on the screen - double densityFactor = Math.Max(0, density - 1) / 4; + double densityFactor = Math.Max(0, currObj.Density - 1) / 4; // This is kinda wrong cuz it returns value bigger than preempt // double timeSpentInvisible = getDurationSpentInvisible(currObj) / 1000 / currObj.ClockRate; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 47efc28cbb35..640c2c1ab46e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -90,6 +90,11 @@ public class OsuDifficultyHitObject : DifficultyHitObject /// public double RhythmDifficulty { get; private set; } + /// + /// Density of the object for given preempt. Saved for optimization, density calculation is expensive. + /// + public double Density { get; private set; } + /// /// Objects that was visible after the note was hit together with cumulative overlapping difficulty. Saved for optimization to avoid O(x^4) time complexity. /// @@ -140,8 +145,13 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje setDistances(clockRate); RhythmDifficulty = RhythmEvaluator.EvaluateDifficultyOf(this); + Density = ReadingEvaluator.EvaluateDensityOf(this); + OverlapObjects = getOverlapObjects(); + } - OverlapObjects = new List(); + private List getOverlapObjects() + { + List overlapObjects = new List(); double totalOverlapnessDifficulty = 0; double currentTime = DeltaTime; @@ -204,9 +214,11 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje } totalOverlapnessDifficulty += currentOverlapness; - OverlapObjects.Add(new OverlapObject(loopObj, totalOverlapnessDifficulty)); + overlapObjects.Add(new OverlapObject(loopObj, totalOverlapnessDifficulty)); prevObject = loopObj; } + + return overlapObjects; } private static double getSimilarity(double timeA, double timeB) From 71df6595339fc06c6c221ea100e92fe7d9585c76 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 26 Mar 2024 16:38:28 +0200 Subject: [PATCH 68/96] bandaid for Rainbow Dash +EZ --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 6 ++-- .../Preprocessing/OsuDifficultyHitObject.cs | 33 +++++++++++++++++-- .../Difficulty/Skills/OsuStrainSkill.cs | 1 - 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 0987d3630a3a..dfeb09a6cba0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -182,7 +182,7 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) screenOverlapDifficulty += lastOverlapness; // This is a correct way to do this (paired with changing >= to <=), but somehow it get's more broken - //screenOverlapDifficulty = Math.Max(screenOverlapDifficulty, lastOverlapness); + // screenOverlapDifficulty = Math.Max(screenOverlapDifficulty, lastOverlapness); } return overlap_multiplier * Math.Max(0, screenOverlapDifficulty - 0.7); @@ -330,8 +330,10 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) { var currObj = (OsuDifficultyHitObject)current; + double density = ReadingEvaluator.EvaluateDensityOf(current, false); + // Consider that density matters only starting from 3rd note on the screen - double densityFactor = Math.Max(0, currObj.Density - 1) / 4; + double densityFactor = Math.Max(0, density - 1) / 4; // This is kinda wrong cuz it returns value bigger than preempt // double timeSpentInvisible = getDurationSpentInvisible(currObj) / 1000 / currObj.ClockRate; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 640c2c1ab46e..603142e37ac2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -146,6 +146,7 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje RhythmDifficulty = RhythmEvaluator.EvaluateDifficultyOf(this); Density = ReadingEvaluator.EvaluateDensityOf(this); + OverlapObjects = getOverlapObjects(); } @@ -156,6 +157,7 @@ private List getOverlapObjects() double totalOverlapnessDifficulty = 0; double currentTime = DeltaTime; List historicTimes = new List(); + List historicAngles = new List(); OsuDifficultyHitObject prevObject = this; @@ -164,12 +166,21 @@ private List getOverlapObjects() // Overlapness with this object double currentOverlapness = calculateOverlapness(this, loopObj); + if (prevObject.Angle.IsNull()) + { + currentTime += prevObject.DeltaTime; + continue; + } + + // Previous angle because order is reversed; + double angle = (double)prevObject.Angle; + // Overlapness between current and prev to make streams have 0 buff double instantOverlapness = 0.5 + calculateOverlapness(prevObject, loopObj); // Nerf overlaps on wide angles double angleFactor = 1; - if (prevObject.Angle != null) angleFactor += (-Math.Cos((double)prevObject.Angle) + 1) / 2; // =2 for wide angles, =1 for acute angles + angleFactor += (-Math.Cos(angle) + 1) / 2; // =2 for wide angles, =1 for acute angles instantOverlapness = Math.Min(1, instantOverlapness * angleFactor); // wide angles are more predictable currentOverlapness *= (1 - instantOverlapness) * 2; // wide angles will have close-to-zero buff @@ -192,10 +203,14 @@ private List getOverlapObjects() cumulativeTimeWithoutCurrent += historicTimes[j]; // Check how similar cumulative times are - currentMinOverlapness = Math.Min(currentMinOverlapness, currentOverlapness * getSimilarity(cumulativeTimeWithCurrent, cumulativeTimeWithoutCurrent)); + double potentialMinOverlapness = currentOverlapness * getSimilarity(cumulativeTimeWithCurrent, cumulativeTimeWithoutCurrent); + potentialMinOverlapness *= getAngleDifference(angle, historicAngles[j]); + currentMinOverlapness = Math.Min(currentMinOverlapness, potentialMinOverlapness); // Check how similar current time with cumulative time - currentMinOverlapness = Math.Min(currentMinOverlapness, currentOverlapness * getSimilarity(currentTime, cumulativeTimeWithoutCurrent)); + potentialMinOverlapness = currentOverlapness * getSimilarity(currentTime, cumulativeTimeWithoutCurrent); + potentialMinOverlapness *= getAngleDifference(angle, historicAngles[j]); + currentMinOverlapness = Math.Min(currentMinOverlapness, potentialMinOverlapness); // Starting from this point - we will never have better match, so stop searching if (cumulativeTimeWithoutCurrent >= cumulativeTimeWithCurrent) @@ -205,7 +220,10 @@ private List getOverlapObjects() } currentOverlapness = currentMinOverlapness; + historicTimes.Add(currentTime); + historicAngles.Add(angle); + currentTime = prevObject.DeltaTime; } else @@ -232,6 +250,15 @@ private static double getSimilarity(double timeA, double timeB) return (Math.Cos((similarity - 0.75) * Math.PI / 0.15) + 1) / 2; // drops from 1 to 0 as similarity increase from 0.75 to 0.9 } + private static double getAngleDifference(double angle1, double angle2) + { + double difference = Math.Abs(angle1 - angle2); + double threeshold = Math.PI / 12; + + if (difference > threeshold) return 1; + return difference / threeshold; + } + private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDifficultyHitObject odho2) { const double area_coef = 0.85; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 353ccc4d8083..103ca5571f49 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Mods; using System.Linq; using osu.Framework.Utils; -using System.Xml.Linq; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { From 23808be9b28308b8ba4cb1a75ca2e98b74921959 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 30 Mar 2024 14:16:27 +0200 Subject: [PATCH 69/96] new experimental overlap summing --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 21 +++++++++++++++---- .../Difficulty/Skills/Flashlight.cs | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index dfeb09a6cba0..6d9b10092c1b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -16,7 +16,7 @@ public static class ReadingEvaluator { private const double reading_window_size = 3000; - private const double overlap_multiplier = 3.5; + private const double overlap_multiplier = 1.8; //3.5 public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDistanceNerf = true) { @@ -171,21 +171,34 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) var currObj = (OsuDifficultyHitObject)current; double screenOverlapDifficulty = 0; + List overlapDifficulties = new List(); + foreach (var loopObj in retrievePastVisibleObjects(currObj)) { double lastOverlapness = 0; foreach (var overlapObj in loopObj.OverlapObjects) { - if (overlapObj.HitObject.StartTime + overlapObj.HitObject.Preempt >= currObj.StartTime) break; + if (overlapObj.HitObject.StartTime + overlapObj.HitObject.Preempt <= currObj.StartTime) break; lastOverlapness = overlapObj.Overlapness; } - screenOverlapDifficulty += lastOverlapness; + + overlapDifficulties.Add(lastOverlapness); + //screenOverlapDifficulty += lastOverlapness; // This is a correct way to do this (paired with changing >= to <=), but somehow it get's more broken // screenOverlapDifficulty = Math.Max(screenOverlapDifficulty, lastOverlapness); } - return overlap_multiplier * Math.Max(0, screenOverlapDifficulty - 0.7); + const double decay_weight = 0.5; + double weight = 1.0; + + foreach (double difficulty in overlapDifficulties.OrderDescending()) + { + screenOverlapDifficulty += difficulty * weight; + weight *= decay_weight; + } + + return overlap_multiplier * Math.Max(0, screenOverlapDifficulty - 1.2); } public static double EvaluateDifficultyOf(DifficultyHitObject current) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index c30246df15f9..9d2719449a6b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -25,7 +25,7 @@ public Flashlight(Mod[] mods) hasHiddenMod = mods.Any(m => m is OsuModHidden); } - private double skillMultiplier => 0.054; + private double skillMultiplier => 0.05; private double strainDecayBase => 0.15; private double currentStrain; From 2dbdd4f7d7807962a32ff3ad37765a8746693452 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 4 Apr 2024 18:36:58 +0300 Subject: [PATCH 70/96] balancing --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 16 ++++------------ .../Difficulty/OsuDifficultyCalculator.cs | 7 ++++--- .../Difficulty/OsuPerformanceCalculator.cs | 10 +++++----- .../Preprocessing/OsuDifficultyHitObject.cs | 13 +++++++++++-- .../Difficulty/Skills/Reading.cs | 12 +++++++----- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 6d9b10092c1b..35764e0aeb2e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -16,7 +16,7 @@ public static class ReadingEvaluator { private const double reading_window_size = 3000; - private const double overlap_multiplier = 1.8; //3.5 + private const double overlap_multiplier = 1.8; public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDistanceNerf = true) { @@ -38,7 +38,7 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - if (applyDistanceNerf) loopDifficulty *= (logistic((loopObj.MinimumJumpDistance - 60) / 10) + 0.2) / 1.2; + if (applyDistanceNerf) loopDifficulty *= (logistic((loopObj.MinimumJumpDistance - 80) / 10) + 0.2) / 1.2; // Reduce density bonus for this object if they're too apart in time // Nerf starts on 1500ms and reaches maximum (*=0) on 3000ms @@ -122,7 +122,7 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi // Bandaid to fix Rubik's Cube +EZ double wideness = 0; - if (loopObj.Angle.Value > Math.PI * 0.5) + if (loopObj.Angle!.Value > Math.PI * 0.5) { // Goes from 0 to 1 as angle increasing from 90 degrees to 180 wideness = (loopObj.Angle.Value / Math.PI - 0.5) * 2; @@ -183,10 +183,6 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) } overlapDifficulties.Add(lastOverlapness); - //screenOverlapDifficulty += lastOverlapness; - - // This is a correct way to do this (paired with changing >= to <=), but somehow it get's more broken - // screenOverlapDifficulty = Math.Max(screenOverlapDifficulty, lastOverlapness); } const double decay_weight = 0.5; @@ -217,9 +213,6 @@ public static double EvaluateAimingDensityFactorOf(DifficultyHitObject current) { double difficulty = ((OsuDifficultyHitObject)current).Density; - double overlapBonus = EvaluateOverlapDifficultyOf(current) * difficulty; - difficulty += overlapBonus * 0.1; // Overlaps should affect aiming part much less - return Math.Max(0, Math.Pow(difficulty, 1.5) - 1); } @@ -353,8 +346,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) // The closer timeSpentInvisible is to 0 -> the less difference there are between NM and HD // So we will reduce base according to this - // It will be 0.354 on AR11 value - double invisibilityFactor = logistic(currObj.Preempt / 160 - 4); + double invisibilityFactor = logistic(currObj.Preempt / 180 - 3.5); double hdDifficulty = invisibilityFactor + densityFactor; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 10e6b4e5a24e..3b58cdab110a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -22,9 +22,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyCalculator : DifficultyCalculator { - public const double DIFFICULTY_MULTIPLIER = 0.067; + public const double DIFFICULTY_MULTIPLIER = 0.0668; public const double SUM_POWER = 1.1; - public const double FL_SUM_POWER = 1.4; + public const double FL_SUM_POWER = 1.5; + public const double AR_SUM_POWER = 2.0; public override int Version => 20220902; public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) @@ -73,7 +74,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating); double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating) * 0.5; // WARNING, this is purely visual change to reduce SR inflation on high-end - double baseReadingARPerformance = Math.Max(baseReadingLowARPerformance, baseReadingHighARPerformance); + double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, AR_SUM_POWER) + Math.Pow(baseReadingHighARPerformance, AR_SUM_POWER), 1.0 / AR_SUM_POWER); double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FL_SUM_POWER) + Math.Pow(baseReadingARPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 16d15a7ea711..ba5343055807 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -82,12 +82,12 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s flashlightValue = 0.0; double lowARValue = computeReadingLowARValue(score, osuAttributes); - double readingHDValue = computeReadingHiddenValue(score, osuAttributes); - double highARValue = computeReadingHighARValue(score, osuAttributes); - // Take only max to reduce pp inflation - double readingARValue = Math.Max(lowARValue, highARValue); + double arPower = OsuDifficultyCalculator.AR_SUM_POWER; + double readingARValue = Math.Pow(Math.Pow(lowARValue, arPower) + Math.Pow(highARValue, arPower), 1.0 / arPower); + + double readingHDValue = computeReadingHiddenValue(score, osuAttributes); // Reduce AR reading bonus if FL is present double flPower = OsuDifficultyCalculator.FL_SUM_POWER; @@ -390,7 +390,7 @@ public static double AdjustCognitionPerformance(double cognitionPerformance, dou // Avoid it being broken on millions of pp, ruins it being continious, but it will never happen on normal circumstances if (capPerformance > 10000 || cognitionPerformance > 10000) cognitionPerformance = Math.Min(capPerformance, cognitionPerformance); - else cognitionPerformance = 1000 * softmin(capPerformance / 1000, cognitionPerformance / 1000, 100); + else cognitionPerformance = 100 * softmin(capPerformance / 100, cognitionPerformance / 100, 100); return cognitionPerformance; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 603142e37ac2..22e95b1fce17 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -75,11 +75,19 @@ public class OsuDifficultyHitObject : DifficultyHitObject public double TravelTime { get; private set; } /// - /// Angle the player has to take to hit this . + /// Absolute angle the player has to take to hit this . /// Calculated as the angle between the circles (current-2, current-1, current). + /// Ranges from 0 to PI /// public double? Angle { get; private set; } + /// + /// Angle the player has to take to hit this . + /// Calculated as the angle between the circles (current-2, current-1, current). + /// Ranges from -PI to PI + /// + public double? AngleSigned { get; private set; } + /// /// Retrieves the full hit window for a Great . /// @@ -404,7 +412,8 @@ private void setDistances(double clockRate) float dot = Vector2.Dot(v1, v2); float det = v1.X * v2.Y - v1.Y * v2.X; - Angle = Math.Abs(Math.Atan2(det, dot)); + AngleSigned = Math.Atan2(det, dot); + Angle = Math.Abs((double)AngleSigned); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 9482b0a41387..64ca049c9b01 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -16,8 +16,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - private double skillMultiplier => 1.04; - private double aimComponentMultiplier => 0.7; + private double skillMultiplier => 1.25; + private double aimComponentMultiplier => 0.4; public ReadingLowAR(Mod[] mods) : base(mods) @@ -83,7 +83,9 @@ public override double DifficultyValue() return difficulty; } - public static double DifficultyToPerformance(double difficulty) => difficulty < 1 ? difficulty * 6.0 : Math.Pow(difficulty, 4) * 6.0; + public static double DifficultyToPerformance(double difficulty) => Math.Max( + Math.Max(Math.Pow(difficulty, 1) * 13.0, Math.Pow(difficulty, 2) * 13.0), + Math.Max(Math.Pow(difficulty, 3) * 9.0, Math.Pow(difficulty, 4) * 6.0)); } public class ReadingHidden : OsuStrainSkill @@ -94,7 +96,7 @@ public ReadingHidden(Mod[] mods) } private double currentStrain; - private double skillMultiplier => 4.8; + private double skillMultiplier => 3.8; protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); @@ -112,6 +114,6 @@ protected override double StrainValueAt(DifficultyHitObject current) return currentStrain; } - public new static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 2) * 25.0; + public new static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 1.8) * 28.0; } } From ae4f0a10acf1b4d44b03741a6f146223cf45e054 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 7 Apr 2024 22:28:55 +0300 Subject: [PATCH 71/96] Many changes 1) High AR nerf 2) Overlap fix 3) Optimisation --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 176 ++++++------------ .../Preprocessing/OsuDifficultyHitObject.cs | 127 +++++++++++-- .../Difficulty/Skills/ReadingHighAR.cs | 4 +- 3 files changed, 179 insertions(+), 128 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 35764e0aeb2e..e4598a46d555 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -16,22 +16,23 @@ public static class ReadingEvaluator { private const double reading_window_size = 3000; - private const double overlap_multiplier = 1.8; + private const double overlap_multiplier = 1.0; public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDistanceNerf = true) { var currObj = (OsuDifficultyHitObject)current; + double density = 0; double densityAnglesNerf = -2.5; // we have threshold of 2.5 OsuDifficultyHitObject? prevObj0 = null; - OsuDifficultyHitObject? prevObj1 = null; - OsuDifficultyHitObject? prevObj2 = null; double prevAngleNerf = 1; - foreach (var loopObj in retrievePastVisibleObjects(currObj).Reverse()) + foreach (var readingpObj in currObj.ReadingObjects) { + var loopObj = readingpObj.HitObject; + if (loopObj.Index < 1) continue; // Don't look on the first object of the map @@ -48,12 +49,6 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi if (prevObj0.IsNull()) prevObj0 = (OsuDifficultyHitObject)loopObj.Previous(0); - if (prevObj1.IsNull()) - prevObj1 = (OsuDifficultyHitObject?)loopObj.Previous(1); - - if (prevObj2.IsNull()) - prevObj2 = (OsuDifficultyHitObject?)loopObj.Previous(2); - // Only if next object is slower, representing break from many notes in a row if (loopObj.StrainTime > prevObj0.StrainTime) { @@ -72,92 +67,32 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi density += loopDifficulty; // Angles nerf + double currAngleNerf = (loopObj.AnglePredictability / 2) + 0.5; - if (loopObj.Angle.IsNotNull() && prevObj0.IsNotNull() && prevObj0.Angle.IsNotNull()) - { - double angleDifference = Math.Abs(prevObj0.Angle.Value - loopObj.Angle.Value); - - // Assume that very low spacing difference means that angles don't matter - if (prevObj0.LazyJumpDistance < OsuDifficultyHitObject.NORMALISED_RADIUS) - angleDifference *= Math.Pow(prevObj0.LazyJumpDistance / OsuDifficultyHitObject.NORMALISED_RADIUS, 2); - if (loopObj.LazyJumpDistance < OsuDifficultyHitObject.NORMALISED_RADIUS) - angleDifference *= Math.Pow(loopObj.LazyJumpDistance / OsuDifficultyHitObject.NORMALISED_RADIUS, 2); - - // assume worst-case if no angles - double angleDifference1 = 0; - double angleDifference2 = 0; - - // Nerf alternating angles case - if (prevObj1.IsNotNull() && prevObj2.IsNotNull() && prevObj1.Angle.IsNotNull() && prevObj2.Angle.IsNotNull()) - { - // Normalized difference - angleDifference1 = Math.Abs(prevObj1.Angle.Value - loopObj.Angle.Value) / Math.PI; - angleDifference2 = Math.Abs(prevObj2.Angle.Value - prevObj0.Angle.Value) / Math.PI; - } - - // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 - double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); - - // Be sure to nerf only same rhythms - double rhythmFactor = 1 - getRhythmDifference(loopObj.StrainTime, prevObj0.StrainTime); // 0 on different rhythm, 1 on same rhythm - - if (prevObj1.IsNotNull()) - rhythmFactor *= 1 - getRhythmDifference(prevObj0.StrainTime, prevObj1.StrainTime); - if (prevObj1.IsNotNull() && prevObj2.IsNotNull()) - rhythmFactor *= 1 - getRhythmDifference(prevObj1.StrainTime, prevObj2.StrainTime); + // Apply the nerf only when it's repeated + double angleNerf = Math.Min(currAngleNerf, prevAngleNerf); - // double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevObj0.Angle.Value) / Math.PI; + // Reduce angles nerf if objects are too apart in time + // Angle nerf is starting being reduced from 200ms (150BPM jump) and it reduced to 0 on 2000ms + //double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - 200) / (2000 - 200), 0, 1); - double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); - - prevAngleAdjust *= alternatingFactor; // Nerf if alternating - prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms - // prevAngleAdjust *= acuteAngleFactor; // no longer needed? - - angleDifference -= prevAngleAdjust; - - // Reduce angles nerf if objects are too apart in time - // Angle nerf is starting being reduced from 200ms (150BPM jump) and it reduced to 0 on 2000ms - double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - 200) / (2000 - 200), 0, 1); - - // Bandaid to fix Rubik's Cube +EZ - double wideness = 0; - if (loopObj.Angle!.Value > Math.PI * 0.5) - { - // Goes from 0 to 1 as angle increasing from 90 degrees to 180 - wideness = (loopObj.Angle.Value / Math.PI - 0.5) * 2; - - // Transform into cubic scaling - wideness = 1 - Math.Pow(1 - wideness, 3); - } - - // Angle difference will be considered as 2 times lower if angle is wide - angleDifference /= 1 + wideness; - - // Current angle nerf. Angle difference more than 15 degrees gets no penalty - double adjustedAngleDifference = Math.Min(Math.PI / 12, angleDifference); + // Bandaid to fix Rubik's Cube +EZ + double wideness = 0; + if (loopObj.Angle.IsNotNull() && loopObj.Angle.Value > Math.PI * 0.5) + { + // Goes from 0 to 1 as angle increasing from 90 degrees to 180 + wideness = (loopObj.Angle.Value / Math.PI - 0.5) * 2; - // WARNING - this thing always gives at least 0.5 angle nerf, this is a bug, but removing it completely ruins everything - // Theoretically - this issue is fixable by changing multipliers everywhere, - // but this is not needed because this bug have no drawbacks outside of algorithm not working as intended - double currAngleNerf = Math.Cos(Math.Min(Math.PI / 2, 4 * adjustedAngleDifference)); + // Transform into cubic scaling + wideness = 1 - Math.Pow(1 - wideness, 3); + } - // Apply the nerf only when it's repeated - double angleNerf = Math.Min(currAngleNerf, prevAngleNerf); + // But only for sharp angles + angleNerf += wideness * (currAngleNerf - angleNerf); - // But only for sharp angles - angleNerf += wideness * (currAngleNerf - angleNerf); + densityAnglesNerf += Math.Min(angleNerf, loopDifficulty); + prevAngleNerf = currAngleNerf; - densityAnglesNerf += Math.Min(angleNerf, loopDifficulty); - prevAngleNerf = currAngleNerf; - } - else // Assume worst-case if no angles - { - densityAnglesNerf += loopDifficulty; - } - - prevObj2 = prevObj1; - prevObj1 = prevObj0; prevObj0 = loopObj; } @@ -171,30 +106,60 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) var currObj = (OsuDifficultyHitObject)current; double screenOverlapDifficulty = 0; - List overlapDifficulties = new List(); + if (currObj.ReadingObjects.Count == 0) + return 0; + + var overlapDifficulties = new List<(OsuDifficultyHitObject HitObject, double Difficulty)>(); - foreach (var loopObj in retrievePastVisibleObjects(currObj)) + // Find initial overlap values + foreach (var loopObj in currObj.ReadingObjects) { double lastOverlapness = 0; - foreach (var overlapObj in loopObj.OverlapObjects) + foreach (var overlapObj in loopObj.HitObject.ReadingObjects) { if (overlapObj.HitObject.StartTime + overlapObj.HitObject.Preempt <= currObj.StartTime) break; lastOverlapness = overlapObj.Overlapness; } - overlapDifficulties.Add(lastOverlapness); + if (lastOverlapness > 0) overlapDifficulties.Add((loopObj.HitObject, lastOverlapness)); + } + + var sortedDifficulties = overlapDifficulties.OrderByDescending(d => d.Difficulty); + + for (int i = 0; i < sortedDifficulties.Count(); i++) + { + var harderObject = sortedDifficulties.ElementAt(i); + + // Look for all easier objects + for (int j = i + 1; j < sortedDifficulties.Count(); j++) + { + var easierObject = sortedDifficulties.ElementAt(j); + + // Get the overlap value + double overlapValue; + + // OverlapValues dict only contains prev objects, so be sure to use right object + if (harderObject.HitObject.Index > easierObject.HitObject.Index) + overlapValue = harderObject.HitObject.OverlapValues[easierObject.HitObject]; + else + overlapValue = easierObject.HitObject.OverlapValues[harderObject.HitObject]; + + // Nerf easier object if it overlaps in the same place as hard one + easierObject.Difficulty *= Math.Pow(1 - overlapValue, 2); + } } const double decay_weight = 0.5; double weight = 1.0; - foreach (double difficulty in overlapDifficulties.OrderDescending()) + foreach (var diffObject in sortedDifficulties.OrderByDescending(d => d.Difficulty)) { - screenOverlapDifficulty += difficulty * weight; + // Add weighted difficulty + screenOverlapDifficulty += Math.Max(0, diffObject.Difficulty - 0.5) * weight; weight *= decay_weight; } - return overlap_multiplier * Math.Max(0, screenOverlapDifficulty - 1.2); + return overlap_multiplier * Math.Max(0, screenOverlapDifficulty); } public static double EvaluateDifficultyOf(DifficultyHitObject current) { @@ -304,23 +269,6 @@ private static double getVelocityChangeFactor(OsuDifficultyHitObject osuCurrObj, return velocityChangeFactor; } - // Returns a list of objects that are visible on screen at - // the point in time at which the current object becomes visible. - private static IEnumerable retrievePastVisibleObjects(OsuDifficultyHitObject current) - { - for (int i = 0; i < current.Index; i++) - { - OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i); - - if (hitObject.IsNull() || - current.StartTime - hitObject.StartTime > reading_window_size || - hitObject.StartTime < current.StartTime - current.Preempt) - break; - - yield return hitObject; - } - } - private static double getTimeNerfFactor(double deltaTime) { return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); @@ -393,7 +341,7 @@ public static double GetDifficulty(double preempt) value = Math.Exp(9.07583 - 80.0 * preempt / 3); // EDIT: looks like AR11 getting a bit overnerfed in comparison to other ARs, so i will increase the difference - return Math.Pow(value, 1.4); + return Math.Pow(value, 1.25); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 22e95b1fce17..7d47cc7316be 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -103,10 +103,20 @@ public class OsuDifficultyHitObject : DifficultyHitObject /// public double Density { get; private set; } + /// + /// Predictabiliy of the angle. Gives high values only in exceptionally repetitive patterns. + /// + public double AnglePredictability { get; private set; } + /// /// Objects that was visible after the note was hit together with cumulative overlapping difficulty. Saved for optimization to avoid O(x^4) time complexity. /// - public IList OverlapObjects { get; private set; } + public IList ReadingObjects { get; private set; } + + /// + /// Objects that was visible after the note was hit together with cumulative overlapping difficulty. Saved for optimization to avoid O(x^4) time complexity. + /// + public IDictionary OverlapValues { get; private set; } /// /// Time in ms between appearence of this and moment to click on it. @@ -152,15 +162,18 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje setDistances(clockRate); + AnglePredictability = calculateAnglePredictability(); + + OverlapValues = new Dictionary(); + ReadingObjects = getOverlapObjects(); + RhythmDifficulty = RhythmEvaluator.EvaluateDifficultyOf(this); Density = ReadingEvaluator.EvaluateDensityOf(this); - - OverlapObjects = getOverlapObjects(); } - private List getOverlapObjects() + private List getOverlapObjects() { - List overlapObjects = new List(); + List overlapObjects = new List(); double totalOverlapnessDifficulty = 0; double currentTime = DeltaTime; @@ -174,6 +187,9 @@ private List getOverlapObjects() // Overlapness with this object double currentOverlapness = calculateOverlapness(this, loopObj); + // Save it for future use + OverlapValues[loopObj] = currentOverlapness; + if (prevObject.Angle.IsNull()) { currentTime += prevObject.DeltaTime; @@ -184,7 +200,7 @@ private List getOverlapObjects() double angle = (double)prevObject.Angle; // Overlapness between current and prev to make streams have 0 buff - double instantOverlapness = 0.5 + calculateOverlapness(prevObject, loopObj); + double instantOverlapness = 0.5 + prevObject.OverlapValues[loopObj]; // Nerf overlaps on wide angles double angleFactor = 1; @@ -192,7 +208,7 @@ private List getOverlapObjects() instantOverlapness = Math.Min(1, instantOverlapness * angleFactor); // wide angles are more predictable currentOverlapness *= (1 - instantOverlapness) * 2; // wide angles will have close-to-zero buff - currentOverlapness *= OpacityAt(loopObj.BaseObject.StartTime, false); + currentOverlapness *= getOpacitiyMultiplier(loopObj); // Increase stability by using opacity // Control overlap repetitivness if (currentOverlapness > 0) @@ -240,13 +256,28 @@ private List getOverlapObjects() } totalOverlapnessDifficulty += currentOverlapness; - overlapObjects.Add(new OverlapObject(loopObj, totalOverlapnessDifficulty)); + overlapObjects.Add(new ReadingObject(loopObj, totalOverlapnessDifficulty)); prevObject = loopObj; } return overlapObjects; } + private double getOpacitiyMultiplier(OsuDifficultyHitObject loopObj) + { + const double threshold = 0.3; + + // Get raw opacity + double opacity = OpacityAt(loopObj.BaseObject.StartTime, false); + + opacity = Math.Min(1, opacity + threshold); // object with opacity 0.7 are still perfectly visible + opacity -= threshold; // return opacity 0 objects back to 0 + opacity /= 1 - threshold; // fix scaling to be 0-1 again + opacity = Math.Sqrt(opacity); // change curve + + return opacity; + } + private static double getSimilarity(double timeA, double timeB) { double similarity = Math.Min(timeA, timeB) / Math.Max(timeA, timeB); @@ -304,8 +335,6 @@ private static IEnumerable retrieveCurrentVisibleObjects OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i); if (hitObject.IsNull() || - // (hitObject.StartTime - current.StartTime) > reading_window_size || - //current.StartTime < hitObject.StartTime - hitObject.Preempt) hitObject.StartTime < current.StartTime - current.Preempt) break; @@ -313,6 +342,80 @@ private static IEnumerable retrieveCurrentVisibleObjects } } + private double calculateAnglePredictability() + { + OsuDifficultyHitObject? prevObj0 = (OsuDifficultyHitObject?)Previous(0); + OsuDifficultyHitObject? prevObj1 = (OsuDifficultyHitObject?)Previous(1); + OsuDifficultyHitObject? prevObj2 = (OsuDifficultyHitObject?)Previous(2); + + if (Angle.IsNull() || prevObj0.IsNull() || prevObj0.Angle.IsNull()) + return 1.0; + + double angleDifference = Math.Abs(prevObj0.Angle.Value - Angle.Value); + + // Assume that very low spacing difference means that angles don't matter + if (prevObj0.LazyJumpDistance < NORMALISED_RADIUS) + angleDifference *= Math.Pow(prevObj0.LazyJumpDistance / NORMALISED_RADIUS, 2); + if (LazyJumpDistance < NORMALISED_RADIUS) + angleDifference *= Math.Pow(LazyJumpDistance / NORMALISED_RADIUS, 2); + + // assume worst-case if no angles + double angleDifference1 = 0; + double angleDifference2 = 0; + + // Nerf alternating angles case + if (prevObj1.IsNotNull() && prevObj2.IsNotNull() && prevObj1.Angle.IsNotNull() && prevObj2.Angle.IsNotNull()) + { + // Normalized difference + angleDifference1 = Math.Abs(prevObj1.Angle.Value - Angle.Value) / Math.PI; + angleDifference2 = Math.Abs(prevObj2.Angle.Value - prevObj0.Angle.Value) / Math.PI; + } + + // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 + double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); + + // Be sure to nerf only same rhythms + double rhythmFactor = 1 - getRhythmDifference(StrainTime, prevObj0.StrainTime); // 0 on different rhythm, 1 on same rhythm + + if (prevObj1.IsNotNull()) + rhythmFactor *= 1 - getRhythmDifference(prevObj0.StrainTime, prevObj1.StrainTime); + if (prevObj1.IsNotNull() && prevObj2.IsNotNull()) + rhythmFactor *= 1 - getRhythmDifference(prevObj1.StrainTime, prevObj2.StrainTime); + + double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); + + prevAngleAdjust *= alternatingFactor; // Nerf if alternating + prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms + + angleDifference -= prevAngleAdjust; + + // Bandaid to fix Rubik's Cube +EZ + double wideness = 0; + if (Angle!.Value > Math.PI * 0.5) + { + // Goes from 0 to 1 as angle increasing from 90 degrees to 180 + wideness = (Angle.Value / Math.PI - 0.5) * 2; + + // Transform into cubic scaling + wideness = 1 - Math.Pow(1 - wideness, 3); + } + + // Angle difference will be considered as 2 times lower if angle is wide + angleDifference /= 1 + wideness; + + // Current angle nerf. Angle difference more than 15 degrees gets no penalty + double adjustedAngleDifference = Math.Min(Math.PI / 12, angleDifference); + + // WARNING - this thing always gives at least 0.5 angle nerf, this is a bug, but removing it completely ruins everything + // Theoretically - this issue is fixable by changing multipliers everywhere, + // but this is not needed because this bug have no drawbacks outside of algorithm not working as intended + double currAngleNerf = Math.Cos(Math.Min(Math.PI / 2, 4 * adjustedAngleDifference)); + + return (currAngleNerf - 0.5) * 2; + } + + private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2); + public double OpacityAt(double time, bool hidden) { if (time > BaseObject.StartTime) @@ -535,12 +638,12 @@ private Vector2 getEndCursorPosition(OsuHitObject hitObject) return pos; } - public struct OverlapObject + public struct ReadingObject { public OsuDifficultyHitObject HitObject; public double Overlapness; - public OverlapObject(OsuDifficultyHitObject hitObject, double overlapness) + public ReadingObject(OsuDifficultyHitObject hitObject, double overlapness) { HitObject = hitObject; Overlapness = overlapness; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index 6832993d8c87..99630e13d629 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -88,7 +88,7 @@ public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) private bool adjustHighAR; private double currentStrain; - private double skillMultiplier => 8.9; + private double skillMultiplier => 7; private double defaultValueMultiplier => 25; protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); @@ -108,7 +108,7 @@ protected override double StrainValueAt(DifficultyHitObject current) public class HighARSpeedComponent : OsuStrainSkill { - private double skillMultiplier => 520; + private double skillMultiplier => 7 * 0.017; protected override double StrainDecayBase => 0.3; private double currentStrain; From 801843bce8d33e151a5abc306e2ad8f1b274f16c Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 7 Apr 2024 22:32:15 +0300 Subject: [PATCH 72/96] Update ReadingEvaluator.cs --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index e4598a46d555..0efa89f82cc9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -155,7 +155,7 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) foreach (var diffObject in sortedDifficulties.OrderByDescending(d => d.Difficulty)) { // Add weighted difficulty - screenOverlapDifficulty += Math.Max(0, diffObject.Difficulty - 0.5) * weight; + screenOverlapDifficulty += Math.Max(0, diffObject.Difficulty - 0.6) * weight; weight *= decay_weight; } From a2e4cb8fea71fb892548f8866b4da59d09350cc1 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 7 Apr 2024 22:44:37 +0300 Subject: [PATCH 73/96] Minor fixes --- .../Preprocessing/OsuDifficultyHitObject.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 7d47cc7316be..186aa9318e9c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -227,13 +227,13 @@ private List getOverlapObjects() cumulativeTimeWithoutCurrent += historicTimes[j]; // Check how similar cumulative times are - double potentialMinOverlapness = currentOverlapness * getSimilarity(cumulativeTimeWithCurrent, cumulativeTimeWithoutCurrent); - potentialMinOverlapness *= getAngleDifference(angle, historicAngles[j]); + double potentialMinOverlapness = currentOverlapness * getTimeDifference(cumulativeTimeWithCurrent, cumulativeTimeWithoutCurrent); + potentialMinOverlapness *= 1 - getAngleSimilarity(angle, historicAngles[j]) * (1 - getTimeDifference(loopObj.StrainTime, prevObject.StrainTime)); currentMinOverlapness = Math.Min(currentMinOverlapness, potentialMinOverlapness); // Check how similar current time with cumulative time - potentialMinOverlapness = currentOverlapness * getSimilarity(currentTime, cumulativeTimeWithoutCurrent); - potentialMinOverlapness *= getAngleDifference(angle, historicAngles[j]); + potentialMinOverlapness = currentOverlapness * getTimeDifference(currentTime, cumulativeTimeWithoutCurrent); + potentialMinOverlapness *= 1 - getAngleSimilarity(angle, historicAngles[j]) * (1 - getTimeDifference(loopObj.StrainTime, prevObject.StrainTime)); currentMinOverlapness = Math.Min(currentMinOverlapness, potentialMinOverlapness); // Starting from this point - we will never have better match, so stop searching @@ -278,7 +278,7 @@ private double getOpacitiyMultiplier(OsuDifficultyHitObject loopObj) return opacity; } - private static double getSimilarity(double timeA, double timeB) + private static double getTimeDifference(double timeA, double timeB) { double similarity = Math.Min(timeA, timeB) / Math.Max(timeA, timeB); if (Math.Max(timeA, timeB) == 0) similarity = 1; @@ -289,13 +289,13 @@ private static double getSimilarity(double timeA, double timeB) return (Math.Cos((similarity - 0.75) * Math.PI / 0.15) + 1) / 2; // drops from 1 to 0 as similarity increase from 0.75 to 0.9 } - private static double getAngleDifference(double angle1, double angle2) + private static double getAngleSimilarity(double angle1, double angle2) { double difference = Math.Abs(angle1 - angle2); double threeshold = Math.PI / 12; - if (difference > threeshold) return 1; - return difference / threeshold; + if (difference > threeshold) return 0; + return 1 - difference / threeshold; } private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDifficultyHitObject odho2) @@ -375,12 +375,12 @@ private double calculateAnglePredictability() double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); // Be sure to nerf only same rhythms - double rhythmFactor = 1 - getRhythmDifference(StrainTime, prevObj0.StrainTime); // 0 on different rhythm, 1 on same rhythm + double rhythmFactor = 1 - getTimeDifference(StrainTime, prevObj0.StrainTime); // 0 on different rhythm, 1 on same rhythm if (prevObj1.IsNotNull()) - rhythmFactor *= 1 - getRhythmDifference(prevObj0.StrainTime, prevObj1.StrainTime); + rhythmFactor *= 1 - getTimeDifference(prevObj0.StrainTime, prevObj1.StrainTime); if (prevObj1.IsNotNull() && prevObj2.IsNotNull()) - rhythmFactor *= 1 - getRhythmDifference(prevObj1.StrainTime, prevObj2.StrainTime); + rhythmFactor *= 1 - getTimeDifference(prevObj1.StrainTime, prevObj2.StrainTime); double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); @@ -414,8 +414,6 @@ private double calculateAnglePredictability() return (currAngleNerf - 0.5) * 2; } - private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2); - public double OpacityAt(double time, bool hidden) { if (time > BaseObject.StartTime) From af9255915c18bce99f897433cb4ce4e0262d196f Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 8 Apr 2024 22:10:29 +0300 Subject: [PATCH 74/96] fixed the bug high AR calc haven't accounted for speed difficulty also, minor optimisation --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 4 +++- osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 0efa89f82cc9..472104eb855c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -115,9 +115,11 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) foreach (var loopObj in currObj.ReadingObjects) { double lastOverlapness = 0; + double targetStartTime = currObj.StartTime - currObj.Preempt; + foreach (var overlapObj in loopObj.HitObject.ReadingObjects) { - if (overlapObj.HitObject.StartTime + overlapObj.HitObject.Preempt <= currObj.StartTime) break; + if (overlapObj.HitObject.StartTime <= targetStartTime) break; lastOverlapness = overlapObj.Overlapness; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index 99630e13d629..1cbdfa71d685 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -68,7 +68,7 @@ public override double DifficultyValue() // Length bonus is in SR to not inflate Star Rating short AR11 maps double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount); - totalPerformance *= lengthBonus * lengthBonus; + totalPerformance *= Math.Pow(lengthBonus, 4); // make it bypass sqrt double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance); double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); @@ -88,7 +88,7 @@ public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) private bool adjustHighAR; private double currentStrain; - private double skillMultiplier => 7; + private double skillMultiplier => 5; private double defaultValueMultiplier => 25; protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); @@ -108,7 +108,7 @@ protected override double StrainValueAt(DifficultyHitObject current) public class HighARSpeedComponent : OsuStrainSkill { - private double skillMultiplier => 7 * 0.017; + private double skillMultiplier => 5 * 58.8; protected override double StrainDecayBase => 0.3; private double currentStrain; From faf18e13034ad78035dfe245e085f66aab50f43d Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 9 Apr 2024 00:24:18 +0300 Subject: [PATCH 75/96] heavy optimisations now it's much faster than earlier --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 74 +++++++++++++------ .../Preprocessing/OsuDifficultyHitObject.cs | 53 ++++++++----- 2 files changed, 84 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 472104eb855c..4bc0e9856274 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -29,9 +29,10 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi double prevAngleNerf = 1; - foreach (var readingpObj in currObj.ReadingObjects) + var readingObjects = currObj.ReadingObjects; + for (int i = 0; i < readingObjects.Count; i++) { - var loopObj = readingpObj.HitObject; + var loopObj = readingObjects[i].HitObject; if (loopObj.Index < 1) continue; // Don't look on the first object of the map @@ -110,41 +111,45 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) return 0; var overlapDifficulties = new List<(OsuDifficultyHitObject HitObject, double Difficulty)>(); + var readingObjects = currObj.ReadingObjects; // Find initial overlap values - foreach (var loopObj in currObj.ReadingObjects) + for (int i = 0; i < readingObjects.Count; i++) { - double lastOverlapness = 0; - double targetStartTime = currObj.StartTime - currObj.Preempt; + var loopObj = readingObjects[i].HitObject; + var loopReadingObjects = (List)loopObj.ReadingObjects; - foreach (var overlapObj in loopObj.HitObject.ReadingObjects) - { - if (overlapObj.HitObject.StartTime <= targetStartTime) break; - lastOverlapness = overlapObj.Overlapness; - } + if (loopReadingObjects.Count == 0) + continue; - if (lastOverlapness > 0) overlapDifficulties.Add((loopObj.HitObject, lastOverlapness)); + double targetStartTime = currObj.StartTime - currObj.Preempt; + double overlapness = boundBinarySearch(loopReadingObjects, targetStartTime); + + if (overlapness > 0) overlapDifficulties.Add((loopObj, overlapness)); } - var sortedDifficulties = overlapDifficulties.OrderByDescending(d => d.Difficulty); + if (overlapDifficulties.Count == 0) + return 0; - for (int i = 0; i < sortedDifficulties.Count(); i++) + var sortedDifficulties = overlapDifficulties.OrderByDescending(d => d.Difficulty).ToList(); + + for (int i = 0; i < sortedDifficulties.Count; i++) { - var harderObject = sortedDifficulties.ElementAt(i); + var harderObject = sortedDifficulties[i]; // Look for all easier objects - for (int j = i + 1; j < sortedDifficulties.Count(); j++) + for (int j = i + 1; j < sortedDifficulties.Count; j++) { - var easierObject = sortedDifficulties.ElementAt(j); + var easierObject = sortedDifficulties[j]; // Get the overlap value - double overlapValue; + double overlapValue = 0; // OverlapValues dict only contains prev objects, so be sure to use right object if (harderObject.HitObject.Index > easierObject.HitObject.Index) - overlapValue = harderObject.HitObject.OverlapValues[easierObject.HitObject]; + harderObject.HitObject.OverlapValues.TryGetValue(easierObject.HitObject, out overlapValue); else - overlapValue = easierObject.HitObject.OverlapValues[harderObject.HitObject]; + easierObject.HitObject.OverlapValues.TryGetValue(harderObject.HitObject, out overlapValue); // Nerf easier object if it overlaps in the same place as hard one easierObject.Difficulty *= Math.Pow(1 - overlapValue, 2); @@ -271,13 +276,34 @@ private static double getVelocityChangeFactor(OsuDifficultyHitObject osuCurrObj, return velocityChangeFactor; } - private static double getTimeNerfFactor(double deltaTime) - { - return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); - } - + private static double getTimeNerfFactor(double deltaTime) => Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2); private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); + + // Finds the overlapness of the last object for which StartTime lower than target + private static double boundBinarySearch(List arr, double target) + { + int low = 0; + int mid; + int high = arr.Count; + + int result = -1; + + while (low < high) + { + mid = low + (high - low) / 2; + + if (arr[mid].HitObject.StartTime >= target) + { + result = mid; + low = mid + 1; + } + else high = mid - 1; + } + + if (result == -1) return 0; + return arr[result].Overlapness; + } } public static class ReadingHiddenEvaluator diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 186aa9318e9c..cdaed0001d6c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; @@ -114,7 +115,7 @@ public class OsuDifficultyHitObject : DifficultyHitObject public IList ReadingObjects { get; private set; } /// - /// Objects that was visible after the note was hit together with cumulative overlapping difficulty. Saved for optimization to avoid O(x^4) time complexity. + /// NON ZERO overlap values for each visible object on the moment this object appeared. Saved for optimization. /// public IDictionary OverlapValues { get; private set; } @@ -165,16 +166,14 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje AnglePredictability = calculateAnglePredictability(); OverlapValues = new Dictionary(); - ReadingObjects = getOverlapObjects(); + ReadingObjects = getReadingObjects(); RhythmDifficulty = RhythmEvaluator.EvaluateDifficultyOf(this); Density = ReadingEvaluator.EvaluateDensityOf(this); } - private List getOverlapObjects() + private List getReadingObjects() { - List overlapObjects = new List(); - double totalOverlapnessDifficulty = 0; double currentTime = DeltaTime; List historicTimes = new List(); @@ -182,13 +181,20 @@ private List getOverlapObjects() OsuDifficultyHitObject prevObject = this; - foreach (var loopObj in retrieveCurrentVisibleObjects(this)) + // The fastest way to do it so far, but still, not very fast + var visibleObjects = retrieveCurrentVisibleObjects(this).ToImmutableArray(); + List overlapObjects = new List(visibleObjects.Length); + + //foreach (var loopObj in visibleObjects) + for (int loopIndex = 0; loopIndex < visibleObjects.Length; loopIndex++) { - // Overlapness with this object + var loopObj = visibleObjects[loopIndex]; + + // Overlapness with this object. Very slow because of `StackedPosition` being bad double currentOverlapness = calculateOverlapness(this, loopObj); - // Save it for future use - OverlapValues[loopObj] = currentOverlapness; + // Save it for future use. Saving only non-zero to make it faster (still slow tho) + if (currentOverlapness > 0) OverlapValues[loopObj] = currentOverlapness; if (prevObject.Angle.IsNull()) { @@ -200,19 +206,20 @@ private List getOverlapObjects() double angle = (double)prevObject.Angle; // Overlapness between current and prev to make streams have 0 buff - double instantOverlapness = 0.5 + prevObject.OverlapValues[loopObj]; + prevObject.OverlapValues.TryGetValue(loopObj, out double instantOverlapness); // Nerf overlaps on wide angles double angleFactor = 1; angleFactor += (-Math.Cos(angle) + 1) / 2; // =2 for wide angles, =1 for acute angles - instantOverlapness = Math.Min(1, instantOverlapness * angleFactor); // wide angles are more predictable + instantOverlapness = Math.Min(1, (0.5 + instantOverlapness) * angleFactor); // wide angles are more predictable currentOverlapness *= (1 - instantOverlapness) * 2; // wide angles will have close-to-zero buff - currentOverlapness *= getOpacitiyMultiplier(loopObj); // Increase stability by using opacity // Control overlap repetitivness if (currentOverlapness > 0) { + currentOverlapness *= getOpacitiyMultiplier(loopObj); // Increase stability by using opacity + double currentMinOverlapness = currentOverlapness; double cumulativeTimeWithCurrent = currentTime; @@ -256,7 +263,9 @@ private List getOverlapObjects() } totalOverlapnessDifficulty += currentOverlapness; - overlapObjects.Add(new ReadingObject(loopObj, totalOverlapnessDifficulty)); + + ReadingObject newObj = new ReadingObject(loopObj, totalOverlapnessDifficulty); + overlapObjects.Add(newObj); prevObject = loopObj; } @@ -304,7 +313,11 @@ private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDiff OsuHitObject o1 = odho1.BaseObject, o2 = odho2.BaseObject; - double distance = Vector2.Distance(o1.StackedPosition, o2.StackedPosition); + // This is VERY slow, make it faster somehow + Vector2 o1pos = o1.StackedPosition; + Vector2 o2pos = o2.StackedPosition; + + double distance = Vector2.Distance(o1pos, o2pos); // Distance func is also pretty slow for some reason double radius = o1.Radius; double distance_sqr = distance * distance; @@ -416,7 +429,9 @@ private double calculateAnglePredictability() public double OpacityAt(double time, bool hidden) { - if (time > BaseObject.StartTime) + var baseObject = BaseObject; // Optimization + + if (time > baseObject.StartTime) { // Consider a hitobject as being invisible when its start time is passed. // In reality the hitobject will be visible beyond its start time up until its hittable window has passed, @@ -424,14 +439,14 @@ public double OpacityAt(double time, bool hidden) return 0.0; } - double fadeInStartTime = BaseObject.StartTime - BaseObject.TimePreempt; - double fadeInDuration = BaseObject.TimeFadeInRaw; + double fadeInStartTime = baseObject.StartTime - baseObject.TimePreempt; + double fadeInDuration = baseObject.TimeFadeInRaw; if (hidden) { // Taken from OsuModHidden. - double fadeOutStartTime = BaseObject.StartTime - BaseObject.TimePreempt + BaseObject.TimeFadeInRaw; - double fadeOutDuration = BaseObject.TimePreempt * OsuModHidden.FADE_OUT_DURATION_MULTIPLIER; + double fadeOutStartTime = baseObject.StartTime - baseObject.TimePreempt + baseObject.TimeFadeInRaw; + double fadeOutDuration = baseObject.TimePreempt * OsuModHidden.FADE_OUT_DURATION_MULTIPLIER; return Math.Min ( From 33d1e2fe369dc7366a973a47c978d8a81777b7de Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 9 Apr 2024 01:29:49 +0300 Subject: [PATCH 76/96] bit of high AR balancing --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 2 +- .../Difficulty/Skills/ReadingHighAR.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 4bc0e9856274..842341e1c8be 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -369,7 +369,7 @@ public static double GetDifficulty(double preempt) value = Math.Exp(9.07583 - 80.0 * preempt / 3); // EDIT: looks like AR11 getting a bit overnerfed in comparison to other ARs, so i will increase the difference - return Math.Pow(value, 1.25); + return Math.Pow(value, 1.1); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs index 1cbdfa71d685..909cc175a666 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs @@ -88,8 +88,8 @@ public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) private bool adjustHighAR; private double currentStrain; - private double skillMultiplier => 5; - private double defaultValueMultiplier => 25; + private double skillMultiplier => 4; + private double defaultValueMultiplier => 60; protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); @@ -102,19 +102,19 @@ protected override double StrainValueAt(DifficultyHitObject current) currentStrain += aimDifficulty; - return currentStrain + defaultValueMultiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR); + return currentStrain + defaultValueMultiplier * Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR), 1.5); } } public class HighARSpeedComponent : OsuStrainSkill { - private double skillMultiplier => 5 * 58.8; + private double skillMultiplier => 4 * 58.8; protected override double StrainDecayBase => 0.3; private double currentStrain; private double currentRhythm; - private double defaultValueMultiplier => 25; + private double defaultValueMultiplier => 60; public HighARSpeedComponent(Mod[] mods) : base(mods) @@ -130,12 +130,12 @@ protected override double StrainValueAt(DifficultyHitObject current) currentStrain *= StrainDecay(currObj.StrainTime); double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; - speedDifficulty *= Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, false), 2); + speedDifficulty *= Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current), 2); currentStrain += speedDifficulty; currentRhythm = currObj.RhythmDifficulty; double totalStrain = currentStrain * currentRhythm; - return totalStrain + defaultValueMultiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current); + return totalStrain + defaultValueMultiplier * Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current), 1.5); } } } From 8d080ccd108a10319bf4a36a5b8b8335f74392bd Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 14 Apr 2024 21:11:59 +0300 Subject: [PATCH 77/96] Angle penalty change --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 29 ++--------- .../Preprocessing/OsuDifficultyHitObject.cs | 50 ++++++++++--------- .../Difficulty/Skills/Reading.cs | 2 +- 3 files changed, 31 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 842341e1c8be..23eb2a659994 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -23,12 +23,10 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi var currObj = (OsuDifficultyHitObject)current; double density = 0; - double densityAnglesNerf = -2.5; // we have threshold of 2.5 + double densityAnglesNerf = -2; // we have threshold of 2 OsuDifficultyHitObject? prevObj0 = null; - double prevAngleNerf = 1; - var readingObjects = currObj.ReadingObjects; for (int i = 0; i < readingObjects.Count; i++) { @@ -71,28 +69,9 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi double currAngleNerf = (loopObj.AnglePredictability / 2) + 0.5; // Apply the nerf only when it's repeated - double angleNerf = Math.Min(currAngleNerf, prevAngleNerf); - - // Reduce angles nerf if objects are too apart in time - // Angle nerf is starting being reduced from 200ms (150BPM jump) and it reduced to 0 on 2000ms - //double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - 200) / (2000 - 200), 0, 1); - - // Bandaid to fix Rubik's Cube +EZ - double wideness = 0; - if (loopObj.Angle.IsNotNull() && loopObj.Angle.Value > Math.PI * 0.5) - { - // Goes from 0 to 1 as angle increasing from 90 degrees to 180 - wideness = (loopObj.Angle.Value / Math.PI - 0.5) * 2; - - // Transform into cubic scaling - wideness = 1 - Math.Pow(1 - wideness, 3); - } - - // But only for sharp angles - angleNerf += wideness * (currAngleNerf - angleNerf); + double angleNerf = currAngleNerf; - densityAnglesNerf += Math.Min(angleNerf, loopDifficulty); - prevAngleNerf = currAngleNerf; + densityAnglesNerf += angleNerf * loopDifficulty; prevObj0 = loopObj; } @@ -219,7 +198,7 @@ public static double EvaluateInpredictabilityOf(DifficultyHitObject current) if (osuCurrObj.Angle != null && osuLastObj.Angle != null && currVelocity > 0 && prevVelocity > 0) { - angleChangeBonus = Math.Pow(Math.Sin((double)((osuCurrObj.Angle - osuLastObj.Angle) / 2)), 2); // Also stealed from xexxar + angleChangeBonus = 1 - osuCurrObj.AnglePredictability; angleChangeBonus *= Math.Min(currVelocity, prevVelocity) / Math.Max(currVelocity, prevVelocity); // Prevent cheesing } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index cdaed0001d6c..d2474b1a2d54 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -163,7 +163,7 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje setDistances(clockRate); - AnglePredictability = calculateAnglePredictability(); + AnglePredictability = CalculateAnglePredictability(); OverlapValues = new Dictionary(); ReadingObjects = getReadingObjects(); @@ -355,7 +355,7 @@ private static IEnumerable retrieveCurrentVisibleObjects } } - private double calculateAnglePredictability() + public double CalculateAnglePredictability() { OsuDifficultyHitObject? prevObj0 = (OsuDifficultyHitObject?)Previous(0); OsuDifficultyHitObject? prevObj1 = (OsuDifficultyHitObject?)Previous(1); @@ -372,33 +372,41 @@ private double calculateAnglePredictability() if (LazyJumpDistance < NORMALISED_RADIUS) angleDifference *= Math.Pow(LazyJumpDistance / NORMALISED_RADIUS, 2); - // assume worst-case if no angles - double angleDifference1 = 0; - double angleDifference2 = 0; + // Now research previous angles + double angleDifferencePrev = 0; + + // How close the smallest angle of curr and prev is to 0 + double zeroAngleFactor = 1.0; // Nerf alternating angles case - if (prevObj1.IsNotNull() && prevObj2.IsNotNull() && prevObj1.Angle.IsNotNull() && prevObj2.Angle.IsNotNull()) + if (prevObj1.IsNotNull() && prevObj2.IsNotNull() && prevObj1.Angle.IsNotNull()) { - // Normalized difference - angleDifference1 = Math.Abs(prevObj1.Angle.Value - Angle.Value) / Math.PI; - angleDifference2 = Math.Abs(prevObj2.Angle.Value - prevObj0.Angle.Value) / Math.PI; + angleDifferencePrev = Math.Abs(prevObj1.Angle.Value - Angle.Value); + zeroAngleFactor = Math.Pow(1 - Math.Min(Angle.Value, prevObj0.Angle.Value) / Math.PI, 10); } - // Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0 - double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2); + // Will be close to 1 if angleDifferencePrev is close to 0 + double rescaleFactor = Math.Pow(1 - angleDifferencePrev / Math.PI, 5); - // Be sure to nerf only same rhythms - double rhythmFactor = 1 - getTimeDifference(StrainTime, prevObj0.StrainTime); // 0 on different rhythm, 1 on same rhythm + // 0 on different rhythm, 1 on same rhythm + double rhythmFactor = 1 - getTimeDifference(StrainTime, prevObj0.StrainTime); if (prevObj1.IsNotNull()) rhythmFactor *= 1 - getTimeDifference(prevObj0.StrainTime, prevObj1.StrainTime); if (prevObj1.IsNotNull() && prevObj2.IsNotNull()) rhythmFactor *= 1 - getTimeDifference(prevObj1.StrainTime, prevObj2.StrainTime); - double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0); + // Get the base - how much alternating difference is lower than current difference + double prevAngleAdjust = Math.Max(angleDifference - angleDifferencePrev, 0); + + // Don't apply the nerf when angleDifferencePrev is too high + prevAngleAdjust *= rescaleFactor; + + // Don't apply the nerf if rhythm is changing + prevAngleAdjust *= rhythmFactor; - prevAngleAdjust *= alternatingFactor; // Nerf if alternating - prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms + // Don't apply the nerf if neither of previous angles isn't close to 0 + prevAngleAdjust *= zeroAngleFactor; angleDifference -= prevAngleAdjust; @@ -416,15 +424,9 @@ private double calculateAnglePredictability() // Angle difference will be considered as 2 times lower if angle is wide angleDifference /= 1 + wideness; - // Current angle nerf. Angle difference more than 15 degrees gets no penalty + // Angle difference more than 15 degrees gets no penalty double adjustedAngleDifference = Math.Min(Math.PI / 12, angleDifference); - - // WARNING - this thing always gives at least 0.5 angle nerf, this is a bug, but removing it completely ruins everything - // Theoretically - this issue is fixable by changing multipliers everywhere, - // but this is not needed because this bug have no drawbacks outside of algorithm not working as intended - double currAngleNerf = Math.Cos(Math.Min(Math.PI / 2, 4 * adjustedAngleDifference)); - - return (currAngleNerf - 0.5) * 2; + return rhythmFactor * Math.Cos(Math.Min(Math.PI / 2, 6 * adjustedAngleDifference)); } public double OpacityAt(double time, bool hidden) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 64ca049c9b01..4bea649432d0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - private double skillMultiplier => 1.25; + private double skillMultiplier => 1.23; private double aimComponentMultiplier => 0.4; public ReadingLowAR(Mod[] mods) From 9a7c21b3b39b730cc1a4eb09faafdc02ca0d43c7 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 14 Apr 2024 21:45:43 +0300 Subject: [PATCH 78/96] high AR clean-up and balancing --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 3 +- .../Difficulty/Skills/Aim.cs | 16 +- .../Difficulty/Skills/Reading.cs | 135 +++++++++++++++-- .../Difficulty/Skills/ReadingHighAR.cs | 141 ------------------ .../Difficulty/Skills/Speed.cs | 16 +- 5 files changed, 140 insertions(+), 171 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 23eb2a659994..d7df0bdf1e6f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -347,8 +347,9 @@ public static double GetDifficulty(double preempt) else value = Math.Exp(9.07583 - 80.0 * preempt / 3); + // The power is 2 times higher to compensate sqrt in high AR skill // EDIT: looks like AR11 getting a bit overnerfed in comparison to other ARs, so i will increase the difference - return Math.Pow(value, 1.1); + return Math.Pow(value, 2.2); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 3f6b22bbb199..6929095f218a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -21,21 +21,17 @@ public Aim(Mod[] mods, bool withSliders) private readonly bool withSliders; - private double currentStrain; + protected double CurrentStrain; + protected double SkillMultiplier => 23.55; - private double skillMultiplier => 23.55; - private double strainDecayBase => 0.15; - - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); - - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime); + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => CurrentStrain * StrainDecay(time - current.Previous(0).StartTime); protected override double StrainValueAt(DifficultyHitObject current) { - currentStrain *= strainDecay(current.DeltaTime); - currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier; + CurrentStrain *= StrainDecay(current.DeltaTime); + CurrentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * SkillMultiplier; - return currentStrain; + return CurrentStrain; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 4bea649432d0..73496e474cf2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -9,6 +9,8 @@ using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -88,32 +90,143 @@ public static double DifficultyToPerformance(double difficulty) => Math.Max( Math.Max(Math.Pow(difficulty, 3) * 9.0, Math.Pow(difficulty, 4) * 6.0)); } - public class ReadingHidden : OsuStrainSkill + public class ReadingHidden : Aim { public ReadingHidden(Mod[] mods) - : base(mods) + : base(mods, false) { } - - private double currentStrain; - private double skillMultiplier => 3.8; - - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); + protected new double SkillMultiplier => 3.8; protected override double StrainValueAt(DifficultyHitObject current) { - currentStrain *= StrainDecay(current.DeltaTime); + CurrentStrain *= StrainDecay(current.DeltaTime); // We're not using slider aim because we assuming that HD doesn't makes sliders harder (what is not true, but we will ignore this for now) double hiddenDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false); hiddenDifficulty *= ReadingHiddenEvaluator.EvaluateDifficultyOf(current); - hiddenDifficulty *= skillMultiplier; + hiddenDifficulty *= SkillMultiplier; - currentStrain += hiddenDifficulty; + CurrentStrain += hiddenDifficulty; - return currentStrain; + return CurrentStrain; } public new static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 1.8) * 28.0; } + + public class ReadingHighAR : GraphSkill + { + + private const double component_multiplier = 0.135; + private const double component_default_value_multiplier = 60; + public ReadingHighAR(Mod[] mods) + : base(mods) + { + aimComponent = new HighARAimComponent(mods); + speedComponent = new HighARSpeedComponent(mods); + } + + private HighARAimComponent aimComponent; + private HighARSpeedComponent speedComponent; + + private readonly List difficulties = new List(); + private int objectsCount = 0; + + public override void Process(DifficultyHitObject current) + { + aimComponent.Process(current); + speedComponent.Process(current); + + if (current.BaseObject is not Spinner) + objectsCount++; + + double power = OsuDifficultyCalculator.SUM_POWER; + double mergedDifficulty = Math.Pow( + Math.Pow(aimComponent.CurrentSectionPeak, power) + + Math.Pow(speedComponent.CurrentSectionPeak, power), 1.0 / power); + + difficulties.Add(mergedDifficulty); + + if (current.Index == 0) + CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; + + while (current.StartTime > CurrentSectionEnd) + { + StrainPeaks.Add(CurrentSectionPeak); + CurrentSectionPeak = 0; + CurrentSectionEnd += SectionLength; + } + + CurrentSectionPeak = Math.Max(mergedDifficulty, CurrentSectionPeak); + } + public override double DifficultyValue() + { + // Simulating summing to get the most correct value possible + double aimValue = Math.Sqrt(aimComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; + double speedValue = Math.Sqrt(speedComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; + + double aimPerformance = OsuStrainSkill.DifficultyToPerformance(aimValue); + double speedPerformance = OsuStrainSkill.DifficultyToPerformance(speedValue); + + double power = OsuDifficultyCalculator.SUM_POWER; + double totalPerformance = Math.Pow(Math.Pow(aimPerformance, power) + Math.Pow(speedPerformance, power), 1.0 / power); + + // Length bonus is in SR to not inflate Star Rating short AR11 maps + double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount); + totalPerformance *= Math.Pow(lengthBonus, 4); // make it bypass sqrt + + double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance); + double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); + + return 53.2 * Math.Sqrt(difficultyValue); + } + + public class HighARAimComponent : Aim + { + public HighARAimComponent(Mod[] mods) + : base(mods, true) + { + } + + protected new double SkillMultiplier => base.SkillMultiplier * component_multiplier; + + protected override double StrainValueAt(DifficultyHitObject current) + { + CurrentStrain *= StrainDecay(current.DeltaTime); + + double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true) * SkillMultiplier; + aimDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current, true); + + CurrentStrain += aimDifficulty; + + return CurrentStrain + component_default_value_multiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current, true); + } + } + + public class HighARSpeedComponent : Speed + { + public HighARSpeedComponent(Mod[] mods) + : base(mods) + { + } + + protected new double SkillMultiplier => base.SkillMultiplier * component_multiplier; + + protected override double StrainValueAt(DifficultyHitObject current) + { + OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current; + + CurrentStrain *= StrainDecay(currObj.StrainTime); + + double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * SkillMultiplier; + speedDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current); + CurrentStrain += speedDifficulty; + + CurrentRhythm = currObj.RhythmDifficulty; + double totalStrain = CurrentStrain * CurrentRhythm; + return totalStrain + component_default_value_multiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current); + } + } + } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs deleted file mode 100644 index 909cc175a666..000000000000 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/ReadingHighAR.cs +++ /dev/null @@ -1,141 +0,0 @@ -// 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 osu.Game.Rulesets.Difficulty.Preprocessing; -using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Difficulty.Evaluators; -using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Difficulty.Skills -{ - public class ReadingHighAR : GraphSkill - { - public ReadingHighAR(Mod[] mods) - : base(mods) - { - aimComponent = new HighARAimComponent(mods); - speedComponent = new HighARSpeedComponent(mods); - } - - private HighARAimComponent aimComponent; - private HighARSpeedComponent speedComponent; - - private readonly List difficulties = new List(); - private int objectsCount = 0; - - public override void Process(DifficultyHitObject current) - { - aimComponent.Process(current); - speedComponent.Process(current); - - if (current.BaseObject is not Spinner) - objectsCount++; - - double power = OsuDifficultyCalculator.SUM_POWER; - double mergedDifficulty = Math.Pow( - Math.Pow(aimComponent.CurrentSectionPeak, power) + - Math.Pow(speedComponent.CurrentSectionPeak, power), 1.0 / power); - - difficulties.Add(mergedDifficulty); - - if (current.Index == 0) - CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; - - while (current.StartTime > CurrentSectionEnd) - { - StrainPeaks.Add(CurrentSectionPeak); - CurrentSectionPeak = 0; - CurrentSectionEnd += SectionLength; - } - - CurrentSectionPeak = Math.Max(mergedDifficulty, CurrentSectionPeak); - } - public override double DifficultyValue() - { - // Simulating summing to get the most correct value possible - double aimValue = Math.Sqrt(aimComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; - double speedValue = Math.Sqrt(speedComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; - - double aimPerformance = OsuStrainSkill.DifficultyToPerformance(aimValue); - double speedPerformance = OsuStrainSkill.DifficultyToPerformance(speedValue); - - double power = OsuDifficultyCalculator.SUM_POWER; - double totalPerformance = Math.Pow(Math.Pow(aimPerformance, power) + Math.Pow(speedPerformance, power), 1.0 / power); - - // Length bonus is in SR to not inflate Star Rating short AR11 maps - double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount); - totalPerformance *= Math.Pow(lengthBonus, 4); // make it bypass sqrt - - double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance); - double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); - - return 53.2 * Math.Sqrt(difficultyValue); - } - } - - public class HighARAimComponent : OsuStrainSkill - { - public HighARAimComponent(Mod[] mods, bool adjustHighAR = true) - : base(mods) - { - this.adjustHighAR = adjustHighAR; - } - - private bool adjustHighAR; - private double currentStrain; - - private double skillMultiplier => 4; - private double defaultValueMultiplier => 60; - - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * StrainDecay(time - current.Previous(0).StartTime); - - protected override double StrainValueAt(DifficultyHitObject current) - { - currentStrain *= StrainDecay(current.DeltaTime); - - double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true) * skillMultiplier; - aimDifficulty *= Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR), 2); - - currentStrain += aimDifficulty; - - return currentStrain + defaultValueMultiplier * Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, adjustHighAR), 1.5); - } - } - - public class HighARSpeedComponent : OsuStrainSkill - { - private double skillMultiplier => 4 * 58.8; - protected override double StrainDecayBase => 0.3; - - private double currentStrain; - private double currentRhythm; - - private double defaultValueMultiplier => 60; - - public HighARSpeedComponent(Mod[] mods) - : base(mods) - { - } - - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * StrainDecay(time - current.Previous(0).StartTime); - - protected override double StrainValueAt(DifficultyHitObject current) - { - OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current; - - currentStrain *= StrainDecay(currObj.StrainTime); - - double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; - speedDifficulty *= Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current), 2); - currentStrain += speedDifficulty; - - currentRhythm = currObj.RhythmDifficulty; - double totalStrain = currentStrain * currentRhythm; - return totalStrain + defaultValueMultiplier * Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current), 1.5); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 9ada2b3c2c39..c360d21bb7c1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -16,11 +16,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : OsuStrainSkill { - private double skillMultiplier => 1375; + protected double SkillMultiplier => 1375; protected override double StrainDecayBase => 0.3; - private double currentStrain; - private double currentRhythm; + protected double CurrentStrain; + protected double CurrentRhythm; protected override int ReducedSectionCount => 5; protected override double DifficultyMultiplier => 1.04; @@ -32,17 +32,17 @@ public Speed(Mod[] mods) { } - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * StrainDecay(time - current.Previous(0).StartTime); + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (CurrentStrain * CurrentRhythm) * StrainDecay(time - current.Previous(0).StartTime); protected override double StrainValueAt(DifficultyHitObject current) { OsuDifficultyHitObject currODHO = (OsuDifficultyHitObject)current; - currentStrain *= StrainDecay(currODHO.StrainTime); - currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; + CurrentStrain *= StrainDecay(currODHO.StrainTime); + CurrentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * SkillMultiplier; - currentRhythm = currODHO.RhythmDifficulty; - double totalStrain = currentStrain * currentRhythm; + CurrentRhythm = currODHO.RhythmDifficulty; + double totalStrain = CurrentStrain * CurrentRhythm; objectStrains.Add(totalStrain); From 84b7bad54a4caa44fe6720eca0ad8e68fd8ce21f Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 15 Apr 2024 13:26:40 +0300 Subject: [PATCH 79/96] minor changes --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++-- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 4 ++-- osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 3b58cdab110a..ba86e4497d3b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -25,7 +25,7 @@ public class OsuDifficultyCalculator : DifficultyCalculator public const double DIFFICULTY_MULTIPLIER = 0.0668; public const double SUM_POWER = 1.1; public const double FL_SUM_POWER = 1.5; - public const double AR_SUM_POWER = 2.0; + public const double AR_SUM_POWER = 1.0; public override int Version => 20220902; public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) @@ -73,7 +73,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating); double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating); - double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating) * 0.5; // WARNING, this is purely visual change to reduce SR inflation on high-end + double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating); // WARNING, this is purely visual change to reduce SR inflation on high-end double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, AR_SUM_POWER) + Math.Pow(baseReadingHighARPerformance, AR_SUM_POWER), 1.0 / AR_SUM_POWER); double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FL_SUM_POWER) + Math.Pow(baseReadingARPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d2474b1a2d54..01771ce5ac18 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -83,8 +83,8 @@ public class OsuDifficultyHitObject : DifficultyHitObject public double? Angle { get; private set; } /// - /// Angle the player has to take to hit this . - /// Calculated as the angle between the circles (current-2, current-1, current). + /// Signed version of the Angle. + /// Potentially should be used for more accurate angle bonuses /// Ranges from -PI to PI /// public double? AngleSigned { get; private set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 73496e474cf2..51f87a7bb1f8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -37,7 +37,7 @@ public override void Process(DifficultyHitObject current) double densityAimingFactor = ReadingEvaluator.EvaluateAimingDensityFactorOf(current); currentDensityAimStrain *= strainDecay(current.DeltaTime); - currentDensityAimStrain += densityAimingFactor * AimEvaluator.EvaluateDifficultyOf(current, false) * aimComponentMultiplier; + currentDensityAimStrain += densityAimingFactor * AimEvaluator.EvaluateDifficultyOf(current, true) * aimComponentMultiplier; double totalDensityDifficulty = (currentDensityAimStrain + densityReadingDifficulty) * skillMultiplier; From 6a4bd648f6c67b518e4e0f9a809ac4720c1399e4 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 16 Apr 2024 17:31:30 +0300 Subject: [PATCH 80/96] better reading cap curve --- .../Difficulty/OsuPerformanceCalculator.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index ba5343055807..5964c1e78b38 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -388,11 +388,12 @@ public static double AdjustCognitionPerformance(double cognitionPerformance, dou // Assuming that less than 25 mechanical pp is not worthy for memory double capPerformance = mechanicalPerformance + flaslightPerformance + 25; - // Avoid it being broken on millions of pp, ruins it being continious, but it will never happen on normal circumstances - if (capPerformance > 10000 || cognitionPerformance > 10000) cognitionPerformance = Math.Min(capPerformance, cognitionPerformance); - else cognitionPerformance = 100 * softmin(capPerformance / 100, cognitionPerformance / 100, 100); + // This thing is kinda unpredictable on cognitionPerformance > capPerformance, because it isn't really a soft min + // But it works well on cognitionPerformance < capPerformance so i will take it + double ratio = capPerformance / cognitionPerformance; + ratio = softmin(ratio, 1, 10); - return cognitionPerformance; + return ratio * cognitionPerformance; } 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); From 463a6cec1ead71ecb213e364200680e2548d5834 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 18 Apr 2024 22:02:24 +0300 Subject: [PATCH 81/96] many HD-related changes --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 12 +----- .../Difficulty/OsuDifficultyCalculator.cs | 41 +++++++++++-------- .../Difficulty/OsuPerformanceCalculator.cs | 15 +++---- .../Difficulty/Skills/Reading.cs | 7 ++-- 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index d7df0bdf1e6f..d77e4e1a3f6e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -294,15 +294,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) double density = ReadingEvaluator.EvaluateDensityOf(current, false); // Consider that density matters only starting from 3rd note on the screen - double densityFactor = Math.Max(0, density - 1) / 4; - - // This is kinda wrong cuz it returns value bigger than preempt - // double timeSpentInvisible = getDurationSpentInvisible(currObj) / 1000 / currObj.ClockRate; - - // The closer timeSpentInvisible is to 0 -> the less difference there are between NM and HD - // So we will reduce base according to this - double invisibilityFactor = logistic(currObj.Preempt / 180 - 3.5); - + double densityFactor = Math.Max(0, density - 1) / 5; + double invisibilityFactor = currObj.Preempt / 1000; double hdDifficulty = invisibilityFactor + densityFactor; // Scale by inpredictability slightly @@ -310,7 +303,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) return hdDifficulty; } - private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); } public static class ReadingHighAREvaluator diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index ba86e4497d3b..dde58f59c718 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -25,7 +25,6 @@ public class OsuDifficultyCalculator : DifficultyCalculator public const double DIFFICULTY_MULTIPLIER = 0.0668; public const double SUM_POWER = 1.1; public const double FL_SUM_POWER = 1.5; - public const double AR_SUM_POWER = 1.0; public override int Version => 20220902; public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) @@ -42,12 +41,13 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double speedNotes = ((Speed)skills[2]).RelevantNoteCount(); - double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * DIFFICULTY_MULTIPLIER; + double hiddenFlashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double readingLowARRating = Math.Sqrt(skills[4].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double readingHighARRating = Math.Sqrt(skills[5].DifficultyValue()) * DIFFICULTY_MULTIPLIER; - double hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * DIFFICULTY_MULTIPLIER; - double hiddenFlashlightRating = Math.Sqrt(skills[7].DifficultyValue()) * DIFFICULTY_MULTIPLIER; + + double hiddenRating = 0; + double flashlightRating = 0; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; @@ -68,20 +68,29 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating); // Cognition + int flIndex = 6; + + double baseReadingHiddenPerformance = 0; + if (mods.Any(h => h is OsuModHidden)) + { + hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * DIFFICULTY_MULTIPLIER; + baseReadingHiddenPerformance = ReadingHidden.DifficultyToPerformance(hiddenRating); + flIndex++; + } + double baseFlashlightPerformance = 0.0; if (mods.Any(h => h is OsuModFlashlight)) + { + flashlightRating = Math.Sqrt(skills[flIndex].DifficultyValue()) * DIFFICULTY_MULTIPLIER; baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating); + } double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating); - double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating); // WARNING, this is purely visual change to reduce SR inflation on high-end - double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, AR_SUM_POWER) + Math.Pow(baseReadingHighARPerformance, AR_SUM_POWER), 1.0 / AR_SUM_POWER); + double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating); + double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SUM_POWER) + Math.Pow(baseReadingHighARPerformance, SUM_POWER), 1.0 / SUM_POWER); double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FL_SUM_POWER) + Math.Pow(baseReadingARPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER); - double baseReadingHiddenPerformance = 0; - if (mods.Any(h => h is OsuModHidden)) - baseReadingHiddenPerformance = ReadingHidden.DifficultyToPerformance(hiddenRating); - double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; @@ -156,16 +165,16 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo new Aim(mods, true), new Aim(mods, false), new Speed(mods), - new Flashlight(mods), + new HiddenFlashlight(mods), new ReadingLowAR(mods), new ReadingHighAR(mods), - new ReadingHidden(mods), - new HiddenFlashlight(mods), }; - // Why adding flashlight one more time???? - //if (mods.Any(h => h is OsuModFlashlight)) - // skills.Add(new Flashlight(mods)); + if (mods.Any(h => h is OsuModHidden)) + skills.Add(new ReadingHidden(mods)); + + if (mods.Any(h => h is OsuModFlashlight)) + skills.Add(new Flashlight(mods)); return skills.ToArray(); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 5964c1e78b38..b6c35561ef71 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -84,8 +84,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double lowARValue = computeReadingLowARValue(score, osuAttributes); double highARValue = computeReadingHighARValue(score, osuAttributes); - double arPower = OsuDifficultyCalculator.AR_SUM_POWER; - double readingARValue = Math.Pow(Math.Pow(lowARValue, arPower) + Math.Pow(highARValue, arPower), 1.0 / arPower); + double readingARValue = Math.Pow(Math.Pow(lowARValue, power) + Math.Pow(highARValue, power), 1.0 / power); double readingHDValue = computeReadingHiddenValue(score, osuAttributes); @@ -348,6 +347,9 @@ private double computeReadingHiddenValue(ScoreInfo score, OsuDifficultyAttribute double rawReading = attributes.HiddenDifficulty; double hiddenValue = ReadingHidden.DifficultyToPerformance(attributes.HiddenDifficulty); + 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)); @@ -388,12 +390,11 @@ public static double AdjustCognitionPerformance(double cognitionPerformance, dou // Assuming that less than 25 mechanical pp is not worthy for memory double capPerformance = mechanicalPerformance + flaslightPerformance + 25; - // This thing is kinda unpredictable on cognitionPerformance > capPerformance, because it isn't really a soft min - // But it works well on cognitionPerformance < capPerformance so i will take it - double ratio = capPerformance / cognitionPerformance; - ratio = softmin(ratio, 1, 10); + double ratio = cognitionPerformance / capPerformance; + if (ratio > 50) return capPerformance; - return ratio * cognitionPerformance; + ratio = softmin(ratio * 10, 10, 5) / 10; + return ratio * capPerformance; } 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); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 51f87a7bb1f8..b9d3676cdc9e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -87,7 +87,7 @@ public override double DifficultyValue() } public static double DifficultyToPerformance(double difficulty) => Math.Max( Math.Max(Math.Pow(difficulty, 1) * 13.0, Math.Pow(difficulty, 2) * 13.0), - Math.Max(Math.Pow(difficulty, 3) * 9.0, Math.Pow(difficulty, 4) * 6.0)); + Math.Max(Math.Pow(difficulty, 3) * 9.00, Math.Pow(difficulty, 4) * 6.0)); } public class ReadingHidden : Aim @@ -96,7 +96,7 @@ public ReadingHidden(Mod[] mods) : base(mods, false) { } - protected new double SkillMultiplier => 3.8; + protected new double SkillMultiplier => 7.2; protected override double StrainValueAt(DifficultyHitObject current) { @@ -112,7 +112,8 @@ protected override double StrainValueAt(DifficultyHitObject current) return CurrentStrain; } - public new static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, 1.8) * 28.0; + public new static double DifficultyToPerformance(double difficulty) => Math.Max( + Math.Max(difficulty * 16, Math.Pow(difficulty, 2) * 10), Math.Pow(difficulty, 3) * 4); } public class ReadingHighAR : GraphSkill From 0d6a4c5c5d486d182eae183a18438c9d06f8d6cb Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 22 Apr 2024 19:40:30 +0300 Subject: [PATCH 82/96] balancing HD AR11 HD nerfed AR0 HD buffed --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 14 ++++++++++++-- osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index d77e4e1a3f6e..824926475f45 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -16,7 +16,7 @@ public static class ReadingEvaluator { private const double reading_window_size = 3000; - private const double overlap_multiplier = 1.0; + private const double overlap_multiplier = 1; public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDistanceNerf = true) { @@ -292,10 +292,20 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) var currObj = (OsuDifficultyHitObject)current; double density = ReadingEvaluator.EvaluateDensityOf(current, false); + double preempt = currObj.Preempt / 1000; // Consider that density matters only starting from 3rd note on the screen double densityFactor = Math.Max(0, density - 1) / 5; - double invisibilityFactor = currObj.Preempt / 1000; + + double invisibilityFactor; + + // AR11+DT and faster = 0 HD pp unless density is big + if (preempt < 0.2) invisibilityFactor = 0; + + // Else accelerating growth until around ART0, then linear, and starting from AR5 is 3 times faster again to buff AR0 +HD + else invisibilityFactor = Math.Min(Math.Pow(preempt * 2.4 - 0.2, 5), Math.Max(preempt, preempt * 3 - 2.4)); + + double hdDifficulty = invisibilityFactor + densityFactor; // Scale by inpredictability slightly diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index b9d3676cdc9e..e2034cacc886 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - private double skillMultiplier => 1.23; + private double skillMultiplier => 1.25; private double aimComponentMultiplier => 0.4; public ReadingLowAR(Mod[] mods) From 81f1136c4fcdb4d640fdf2eb98994c41692663ab Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 25 Apr 2024 17:28:11 +0300 Subject: [PATCH 83/96] added old HD bonus to merge with master --- .../Difficulty/OsuPerformanceCalculator.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b6c35561ef71..b916d234cbba 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -132,6 +132,11 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); + else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) + { + // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. + aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); + } // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = attributes.SliderCount * 0.15; @@ -171,6 +176,11 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. speedValue *= 1.12; } + else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) + { + // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. + speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); + } // Calculate accuracy assuming the worst case scenario double relevantTotalDiff = totalHits - attributes.SpeedNoteCount; @@ -217,6 +227,9 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; + else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) + accuracyValue *= 1.08; + if (score.Mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; From 7750b43ce6fad026f8ddf0d89748c3edda52a8c0 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 25 Apr 2024 17:29:15 +0300 Subject: [PATCH 84/96] Update OsuPerformanceCalculator.cs --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b916d234cbba..82069f152d2a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -226,7 +226,6 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; - else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) accuracyValue *= 1.08; From 3888bd440b41c5cceaf2f2b66cd58ea55f2bc7ed Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 25 Apr 2024 17:31:25 +0300 Subject: [PATCH 85/96] removed old HD bonus --- .../Difficulty/OsuPerformanceCalculator.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 82069f152d2a..1338ad0abbc3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -132,7 +132,7 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); - else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) + else if (score.Mods.Any(m => m is OsuModTraceable)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); @@ -176,7 +176,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. speedValue *= 1.12; } - else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) + else if (score.Mods.Any(m => m is OsuModTraceable)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); @@ -226,8 +226,6 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; - else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) - accuracyValue *= 1.08; if (score.Mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; @@ -236,7 +234,7 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att double visualIndicationBonus = 1.0 + 0.1 * logistic(8.0 - attributes.ApproachRate); accuracyValue *= visualIndicationBonus; - if (score.Mods.Any(h => h is OsuModHidden)) + if (score.Mods.Any(h => h is OsuModHidden || h is OsuModTraceable)) accuracyValue *= visualIndicationBonus; return accuracyValue; From 07cb7011e59f7fa1255253ae820f50afbe5142a5 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 28 Apr 2024 13:01:51 +0300 Subject: [PATCH 86/96] Balancing --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 10 +++------- .../Difficulty/OsuPerformanceCalculator.cs | 6 ++---- osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs | 6 +++--- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 824926475f45..c765095fe70f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -25,7 +24,8 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi double density = 0; double densityAnglesNerf = -2; // we have threshold of 2 - OsuDifficultyHitObject? prevObj0 = null; + // Despite being called prev, it's actually more late in time + OsuDifficultyHitObject prevObj0 = currObj; var readingObjects = currObj.ReadingObjects; for (int i = 0; i < readingObjects.Count; i++) @@ -45,9 +45,6 @@ public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDi double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime; loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); - if (prevObj0.IsNull()) - prevObj0 = (OsuDifficultyHitObject)loopObj.Previous(0); - // Only if next object is slower, representing break from many notes in a row if (loopObj.StrainTime > prevObj0.StrainTime) { @@ -294,8 +291,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) double density = ReadingEvaluator.EvaluateDensityOf(current, false); double preempt = currObj.Preempt / 1000; - // Consider that density matters only starting from 3rd note on the screen - double densityFactor = Math.Max(0, density - 1) / 5; + double densityFactor = Math.Pow(density / 6.2, 1.5); double invisibilityFactor; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 1338ad0abbc3..9a163e3028e3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -231,11 +231,9 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att accuracyValue *= 1.02; // Visual indication bonus - double visualIndicationBonus = 1.0 + 0.1 * logistic(8.0 - attributes.ApproachRate); - - accuracyValue *= visualIndicationBonus; + accuracyValue *= 1.0 + 0.2 * logistic(8.0 - attributes.ApproachRate); if (score.Mods.Any(h => h is OsuModHidden || h is OsuModTraceable)) - accuracyValue *= visualIndicationBonus; + accuracyValue *= 1.0 + 0.1 * logistic(8.0 - attributes.ApproachRate); return accuracyValue; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index e2034cacc886..5bf3340ee30d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingLowAR : GraphSkill { private readonly List difficulties = new List(); - private double skillMultiplier => 1.25; + private double skillMultiplier => 1.26; private double aimComponentMultiplier => 0.4; public ReadingLowAR(Mod[] mods) @@ -86,8 +86,8 @@ public override double DifficultyValue() return difficulty; } public static double DifficultyToPerformance(double difficulty) => Math.Max( - Math.Max(Math.Pow(difficulty, 1) * 13.0, Math.Pow(difficulty, 2) * 13.0), - Math.Max(Math.Pow(difficulty, 3) * 9.00, Math.Pow(difficulty, 4) * 6.0)); + Math.Max(Math.Pow(difficulty, 1.5) * 22, Math.Pow(difficulty, 2) * 22.0), + Math.Max(Math.Pow(difficulty, 3) * 12.0, Math.Pow(difficulty, 4) * 6.00)); } public class ReadingHidden : Aim From 3d9584df561541d1f87d1f5d2e8c22230328c251 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 29 Apr 2024 00:43:16 +0300 Subject: [PATCH 87/96] more balancing and improved low AR acc bonus --- .../Difficulty/OsuPerformanceCalculator.cs | 12 ++++++++++-- osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 9a163e3028e3..6954d1779011 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -231,9 +231,17 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att accuracyValue *= 1.02; // Visual indication bonus - accuracyValue *= 1.0 + 0.2 * logistic(8.0 - attributes.ApproachRate); + double visualBonus = 0.1 * logistic(8.0 - attributes.ApproachRate); + + // Buff if OD is way lower than AR + double ARODDelta = Math.Max(0, attributes.OverallDifficulty - attributes.ApproachRate); + + // This one is goes from 0.0 on delta=0 to 1.0 somewhere around delta=3.4 + double deltaBonus = (1 - Math.Pow(0.95, Math.Pow(ARODDelta, 4))); + + accuracyValue *= 1 + visualBonus * (1 + 2 * deltaBonus); if (score.Mods.Any(h => h is OsuModHidden || h is OsuModTraceable)) - accuracyValue *= 1.0 + 0.1 * logistic(8.0 - attributes.ApproachRate); + accuracyValue *= 1 + visualBonus * (1 + deltaBonus); return accuracyValue; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 5bf3340ee30d..ac3e9e64e9bb 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -86,8 +86,8 @@ public override double DifficultyValue() return difficulty; } public static double DifficultyToPerformance(double difficulty) => Math.Max( - Math.Max(Math.Pow(difficulty, 1.5) * 22, Math.Pow(difficulty, 2) * 22.0), - Math.Max(Math.Pow(difficulty, 3) * 12.0, Math.Pow(difficulty, 4) * 6.00)); + Math.Max(Math.Pow(difficulty, 1.5) * 20, Math.Pow(difficulty, 2) * 17.0), + Math.Max(Math.Pow(difficulty, 3) * 10.5, Math.Pow(difficulty, 4) * 6.00)); } public class ReadingHidden : Aim From e4d8ed9afb11528131806af6b9399e8eb53fddff Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 2 May 2024 16:57:31 +0300 Subject: [PATCH 88/96] optimizations --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 9 +++--- .../Preprocessing/OsuDifficultyHitObject.cs | 32 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index c765095fe70f..e8fb4a846999 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -123,9 +123,9 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) // OverlapValues dict only contains prev objects, so be sure to use right object if (harderObject.HitObject.Index > easierObject.HitObject.Index) - harderObject.HitObject.OverlapValues.TryGetValue(easierObject.HitObject, out overlapValue); + harderObject.HitObject.OverlapValues.TryGetValue(easierObject.HitObject.Index, out overlapValue); else - easierObject.HitObject.OverlapValues.TryGetValue(harderObject.HitObject, out overlapValue); + easierObject.HitObject.OverlapValues.TryGetValue(harderObject.HitObject.Index, out overlapValue); // Nerf easier object if it overlaps in the same place as hard one easierObject.Difficulty *= Math.Pow(1 - overlapValue, 2); @@ -133,12 +133,13 @@ public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) } const double decay_weight = 0.5; + const double threshold = 0.6; double weight = 1.0; - foreach (var diffObject in sortedDifficulties.OrderByDescending(d => d.Difficulty)) + foreach (var diffObject in sortedDifficulties.Where(d => d.Difficulty > threshold).OrderByDescending(d => d.Difficulty)) { // Add weighted difficulty - screenOverlapDifficulty += Math.Max(0, diffObject.Difficulty - 0.6) * weight; + screenOverlapDifficulty += Math.Max(0, diffObject.Difficulty - threshold) * weight; weight *= decay_weight; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 01771ce5ac18..122b41acf31b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -115,9 +115,9 @@ public class OsuDifficultyHitObject : DifficultyHitObject public IList ReadingObjects { get; private set; } /// - /// NON ZERO overlap values for each visible object on the moment this object appeared. Saved for optimization. + /// NON ZERO overlap values for each visible object on the moment this object appeared. Key is . Saved for optimization. /// - public IDictionary OverlapValues { get; private set; } + public IDictionary OverlapValues { get; private set; } /// /// Time in ms between appearence of this and moment to click on it. @@ -165,7 +165,7 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje AnglePredictability = CalculateAnglePredictability(); - OverlapValues = new Dictionary(); + OverlapValues = new Dictionary(); ReadingObjects = getReadingObjects(); RhythmDifficulty = RhythmEvaluator.EvaluateDifficultyOf(this); @@ -181,20 +181,20 @@ private List getReadingObjects() OsuDifficultyHitObject prevObject = this; - // The fastest way to do it so far, but still, not very fast - var visibleObjects = retrieveCurrentVisibleObjects(this).ToImmutableArray(); - List overlapObjects = new List(visibleObjects.Length); + // The fastest way to do it I seen so far. Still - one of the slowest parts of the reading calc + var visibleObjects = retrieveCurrentVisibleObjects(this); + List readingObjects = new List(visibleObjects.Count); //foreach (var loopObj in visibleObjects) - for (int loopIndex = 0; loopIndex < visibleObjects.Length; loopIndex++) + for (int loopIndex = 0; loopIndex < visibleObjects.Count; loopIndex++) { var loopObj = visibleObjects[loopIndex]; // Overlapness with this object. Very slow because of `StackedPosition` being bad double currentOverlapness = calculateOverlapness(this, loopObj); - // Save it for future use. Saving only non-zero to make it faster (still slow tho) - if (currentOverlapness > 0) OverlapValues[loopObj] = currentOverlapness; + // Save it for future use. Saving only non-zero to make it faster + if (currentOverlapness > 0) OverlapValues[loopObj.Index] = currentOverlapness; if (prevObject.Angle.IsNull()) { @@ -206,7 +206,7 @@ private List getReadingObjects() double angle = (double)prevObject.Angle; // Overlapness between current and prev to make streams have 0 buff - prevObject.OverlapValues.TryGetValue(loopObj, out double instantOverlapness); + prevObject.OverlapValues.TryGetValue(loopObj.Index, out double instantOverlapness); // Nerf overlaps on wide angles double angleFactor = 1; @@ -265,11 +265,11 @@ private List getReadingObjects() totalOverlapnessDifficulty += currentOverlapness; ReadingObject newObj = new ReadingObject(loopObj, totalOverlapnessDifficulty); - overlapObjects.Add(newObj); + readingObjects.Add(newObj); prevObject = loopObj; } - return overlapObjects; + return readingObjects; } private double getOpacitiyMultiplier(OsuDifficultyHitObject loopObj) @@ -340,9 +340,11 @@ private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDiff return overlappingAreaNormalized * area_coef + perfectStackBuff * (1 - area_coef); } - private static IEnumerable retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) + private static List retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) { + var visibleObjects = new List(); + for (int i = 0; i < current.Count; i++) { OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i); @@ -351,8 +353,10 @@ private static IEnumerable retrieveCurrentVisibleObjects hitObject.StartTime < current.StartTime - current.Preempt) break; - yield return hitObject; + visibleObjects.Add(hitObject); } + + return visibleObjects; } public double CalculateAnglePredictability() From 86f7b7dea9b1733d7f8d78219f95b17b2157c7d8 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 3 May 2024 00:00:40 +0300 Subject: [PATCH 89/96] optimization and code quality --- .../Preprocessing/OsuDifficultyHitObject.cs | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 122b41acf31b..bf968e21453e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; @@ -34,6 +33,11 @@ public class OsuDifficultyHitObject : DifficultyHitObject /// public readonly double StrainTime; + /// + /// Saved version of to decrease overhead. + /// + public readonly Vector2 StackedPosition; + /// /// Normalised distance from the "lazy" end position of the previous to the start position of this . /// @@ -142,11 +146,14 @@ public class OsuDifficultyHitObject : DifficultyHitObject public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject? lastLastObject, double clockRate, List objects, int index) : base(hitObject, lastObject, clockRate, objects, index) { - this.lastLastObject = lastLastObject as OsuHitObject; + OsuHitObject currObject = (OsuHitObject)hitObject; this.lastObject = (OsuHitObject)lastObject; + this.lastLastObject = lastLastObject as OsuHitObject; + + StackedPosition = currObject.StackedPosition; Preempt = BaseObject.TimePreempt / clockRate; FollowLineTime = 800 / clockRate; // 800ms is follow line appear time - FollowLineTime *= ((OsuHitObject)hitObject).NewCombo ? 0 : 1; // no follow lines when NC + FollowLineTime *= (currObject.NewCombo ? 0 : 1); // no follow lines when NC ClockRate = clockRate; // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. @@ -165,32 +172,33 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje AnglePredictability = CalculateAnglePredictability(); - OverlapValues = new Dictionary(); - ReadingObjects = getReadingObjects(); + (ReadingObjects, OverlapValues) = getReadingObjects(); RhythmDifficulty = RhythmEvaluator.EvaluateDifficultyOf(this); Density = ReadingEvaluator.EvaluateDensityOf(this); } - private List getReadingObjects() + private (IList, IDictionary) getReadingObjects() { double totalOverlapnessDifficulty = 0; double currentTime = DeltaTime; - List historicTimes = new List(); - List historicAngles = new List(); + List historicTimes = []; + List historicAngles = []; OsuDifficultyHitObject prevObject = this; - // The fastest way to do it I seen so far. Still - one of the slowest parts of the reading calc + // The fastest way to do it I've seen so far. Still - one of the slowest parts of the reading calc var visibleObjects = retrieveCurrentVisibleObjects(this); - List readingObjects = new List(visibleObjects.Count); + + var readingObjects = new List(visibleObjects.Count); + OverlapValues = new Dictionary(); //foreach (var loopObj in visibleObjects) for (int loopIndex = 0; loopIndex < visibleObjects.Count; loopIndex++) { var loopObj = visibleObjects[loopIndex]; - // Overlapness with this object. Very slow because of `StackedPosition` being bad + // Overlapness with this object double currentOverlapness = calculateOverlapness(this, loopObj); // Save it for future use. Saving only non-zero to make it faster @@ -206,7 +214,8 @@ private List getReadingObjects() double angle = (double)prevObject.Angle; // Overlapness between current and prev to make streams have 0 buff - prevObject.OverlapValues.TryGetValue(loopObj.Index, out double instantOverlapness); + double instantOverlapness = 0; + prevObject.OverlapValues?.TryGetValue(loopObj.Index, out instantOverlapness); // Nerf overlaps on wide angles double angleFactor = 1; @@ -269,7 +278,7 @@ private List getReadingObjects() prevObject = loopObj; } - return readingObjects; + return (readingObjects, OverlapValues); } private double getOpacitiyMultiplier(OsuDifficultyHitObject loopObj) @@ -311,14 +320,8 @@ private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDiff { const double area_coef = 0.85; - OsuHitObject o1 = odho1.BaseObject, o2 = odho2.BaseObject; - - // This is VERY slow, make it faster somehow - Vector2 o1pos = o1.StackedPosition; - Vector2 o2pos = o2.StackedPosition; - - double distance = Vector2.Distance(o1pos, o2pos); // Distance func is also pretty slow for some reason - double radius = o1.Radius; + double distance = Vector2.Distance(odho1.StackedPosition, odho2.StackedPosition); // Distance func is kinda slow for some reason + double radius = odho1.BaseObject.Radius; double distance_sqr = distance * distance; double radius_sqr = radius * radius; From fd4beb5cdbab30a85e13e7c58b78b0241c5a76eb Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 6 May 2024 00:45:20 +0300 Subject: [PATCH 90/96] lil bit clean-up --- .../Difficulty/OsuPerformanceCalculator.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6954d1779011..d29ff4df87f3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -69,24 +69,25 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s // Cognition - // Preferably this value should be used for capping low AR reading performance - // Because it should have lower cap (you can actually see) - // But it will make formula really weird and overcomplicated - double potentialFlashlightValue = computeFlashlightValue(score, osuAttributes); - // Get HDFL value for capping reading performance + // In theory stuff like AR13, AR13 +HD and AR-INF +HD should use this values + // While AR-INF without HD shoud use normal flashlight values + // Because in first case you're clicking air, while in AR-INF case you're see the notes + // But implementing it is pretty annoying, so I left it "as is" double potentialHiddenFlashlightValue = computeFlashlightValue(score, osuAttributes, true); - double flashlightValue = potentialFlashlightValue; - if (!score.Mods.Any(h => h is OsuModFlashlight)) - flashlightValue = 0.0; - double lowARValue = computeReadingLowARValue(score, osuAttributes); double highARValue = computeReadingHighARValue(score, osuAttributes); double readingARValue = Math.Pow(Math.Pow(lowARValue, power) + Math.Pow(highARValue, power), 1.0 / power); - double readingHDValue = computeReadingHiddenValue(score, osuAttributes); + double flashlightValue = 0; + if (score.Mods.Any(h => h is OsuModFlashlight)) + flashlightValue = computeFlashlightValue(score, osuAttributes); + + double readingHDValue = 0; + if (score.Mods.Any(h => h is OsuModFlashlight)) + readingHDValue = computeReadingHiddenValue(score, osuAttributes); // Reduce AR reading bonus if FL is present double flPower = OsuDifficultyCalculator.FL_SUM_POWER; @@ -97,12 +98,10 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s double accuracyValue = computeAccuracyValue(score, osuAttributes); + // Add cognition value without LP-sum cuz otherwise it makes balancing harder double totalValue = - Math.Pow( - Math.Pow(mechanicalValue, power) + - Math.Pow(accuracyValue, power), 1.0 / power - ) * multiplier; - totalValue += cognitionValue * multiplier; + (Math.Pow(Math.Pow(mechanicalValue, power) + Math.Pow(accuracyValue, power), 1.0 / power) + + cognitionValue) * multiplier; return new OsuPerformanceAttributes { From d50df4e95d0a695d040c1e650c84a1015f3da842 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 6 May 2024 00:47:49 +0300 Subject: [PATCH 91/96] Update OsuPerformanceCalculator.cs --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d29ff4df87f3..fe4cea02c269 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -86,7 +86,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s flashlightValue = computeFlashlightValue(score, osuAttributes); double readingHDValue = 0; - if (score.Mods.Any(h => h is OsuModFlashlight)) + if (score.Mods.Any(h => h is OsuModHidden)) readingHDValue = computeReadingHiddenValue(score, osuAttributes); // Reduce AR reading bonus if FL is present From de912425a2f8e1d24ff2a406977a513a72676311 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 6 May 2024 01:31:19 +0300 Subject: [PATCH 92/96] added TD and RX support --- .../Difficulty/OsuDifficultyCalculator.cs | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index dde58f59c718..bf28c14ed80a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -51,23 +51,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; - if (mods.Any(m => m is OsuModTouchDevice)) - { - aimRating = Math.Pow(aimRating, 0.8); - flashlightRating = Math.Pow(flashlightRating, 0.8); - } - - if (mods.Any(h => h is OsuModRelax)) - { - aimRating *= 0.9; - speedRating = 0.0; - flashlightRating *= 0.7; - } - - double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating); - double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating); - - // Cognition int flIndex = 6; double baseReadingHiddenPerformance = 0; @@ -85,6 +68,29 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating); } + if (mods.Any(m => m is OsuModTouchDevice)) + { + aimRating = Math.Pow(aimRating, 0.8); + readingLowARRating = Math.Pow(readingLowARRating, 0.9); + readingHighARRating = Math.Pow(readingHighARRating, 0.9); + hiddenRating = Math.Pow(hiddenRating, 0.9); + flashlightRating = Math.Pow(flashlightRating, 0.8); + } + + if (mods.Any(h => h is OsuModRelax)) + { + aimRating *= 0.9; + speedRating = 0.0; + readingLowARRating *= 0.95; + readingHighARRating *= 0.7; + hiddenRating *= 0.7; + flashlightRating *= 0.7; + } + + double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating); + double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating); + + // Cognition double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating); double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating); double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SUM_POWER) + Math.Pow(baseReadingHighARPerformance, SUM_POWER), 1.0 / SUM_POWER); From 4f79096cf73962258cef6c30b9c48dcadb83682f Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 6 May 2024 01:45:37 +0300 Subject: [PATCH 93/96] cut out high AR --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 211 ------------------ .../Difficulty/OsuDifficultyCalculator.cs | 33 +-- .../Difficulty/OsuPerformanceCalculator.cs | 88 ++------ .../Preprocessing/OsuDifficultyHitObject.cs | 192 ---------------- .../Difficulty/Skills/Reading.cs | 125 +---------- .../Rulesets/Difficulty/Skills/GraphSkill.cs | 39 ---- .../Rulesets/Difficulty/Skills/StrainSkill.cs | 34 ++- 7 files changed, 49 insertions(+), 673 deletions(-) delete mode 100644 osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index e8fb4a846999..750cb8ef3237 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -2,8 +2,6 @@ // 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.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -13,158 +11,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Main class with some util functions public static class ReadingEvaluator { - private const double reading_window_size = 3000; - - private const double overlap_multiplier = 1; - - public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDistanceNerf = true) - { - var currObj = (OsuDifficultyHitObject)current; - - double density = 0; - double densityAnglesNerf = -2; // we have threshold of 2 - - // Despite being called prev, it's actually more late in time - OsuDifficultyHitObject prevObj0 = currObj; - - var readingObjects = currObj.ReadingObjects; - for (int i = 0; i < readingObjects.Count; i++) - { - var loopObj = readingObjects[i].HitObject; - - if (loopObj.Index < 1) - continue; // Don't look on the first object of the map - - double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); - - // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - if (applyDistanceNerf) loopDifficulty *= (logistic((loopObj.MinimumJumpDistance - 80) / 10) + 0.2) / 1.2; - - // Reduce density bonus for this object if they're too apart in time - // Nerf starts on 1500ms and reaches maximum (*=0) on 3000ms - double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime; - loopDifficulty *= getTimeNerfFactor(timeBetweenCurrAndLoopObj); - - // Only if next object is slower, representing break from many notes in a row - if (loopObj.StrainTime > prevObj0.StrainTime) - { - // Get rhythm similarity: 1 on same rhythms, 0.5 on 1/4 to 1/2 - double rhythmSimilarity = 1 - getRhythmDifference(loopObj.StrainTime, prevObj0.StrainTime); - - // Make differentiation going from 1/4 to 1/2 and bigger difference - // To 1/3 to 1/2 and smaller difference - rhythmSimilarity = Math.Clamp(rhythmSimilarity, 0.5, 0.75); - rhythmSimilarity = 4 * (rhythmSimilarity - 0.5); - - // Reduce density for this objects if rhythms are different - loopDifficulty *= rhythmSimilarity; - } - - density += loopDifficulty; - - // Angles nerf - double currAngleNerf = (loopObj.AnglePredictability / 2) + 0.5; - - // Apply the nerf only when it's repeated - double angleNerf = currAngleNerf; - - densityAnglesNerf += angleNerf * loopDifficulty; - - prevObj0 = loopObj; - } - - // Apply angles nerf - density -= Math.Max(0, densityAnglesNerf); - return density; - } - - public static double EvaluateOverlapDifficultyOf(DifficultyHitObject current) - { - var currObj = (OsuDifficultyHitObject)current; - double screenOverlapDifficulty = 0; - - if (currObj.ReadingObjects.Count == 0) - return 0; - - var overlapDifficulties = new List<(OsuDifficultyHitObject HitObject, double Difficulty)>(); - var readingObjects = currObj.ReadingObjects; - - // Find initial overlap values - for (int i = 0; i < readingObjects.Count; i++) - { - var loopObj = readingObjects[i].HitObject; - var loopReadingObjects = (List)loopObj.ReadingObjects; - - if (loopReadingObjects.Count == 0) - continue; - - double targetStartTime = currObj.StartTime - currObj.Preempt; - double overlapness = boundBinarySearch(loopReadingObjects, targetStartTime); - - if (overlapness > 0) overlapDifficulties.Add((loopObj, overlapness)); - } - - if (overlapDifficulties.Count == 0) - return 0; - - var sortedDifficulties = overlapDifficulties.OrderByDescending(d => d.Difficulty).ToList(); - - for (int i = 0; i < sortedDifficulties.Count; i++) - { - var harderObject = sortedDifficulties[i]; - - // Look for all easier objects - for (int j = i + 1; j < sortedDifficulties.Count; j++) - { - var easierObject = sortedDifficulties[j]; - - // Get the overlap value - double overlapValue = 0; - - // OverlapValues dict only contains prev objects, so be sure to use right object - if (harderObject.HitObject.Index > easierObject.HitObject.Index) - harderObject.HitObject.OverlapValues.TryGetValue(easierObject.HitObject.Index, out overlapValue); - else - easierObject.HitObject.OverlapValues.TryGetValue(harderObject.HitObject.Index, out overlapValue); - - // Nerf easier object if it overlaps in the same place as hard one - easierObject.Difficulty *= Math.Pow(1 - overlapValue, 2); - } - } - - const double decay_weight = 0.5; - const double threshold = 0.6; - double weight = 1.0; - - foreach (var diffObject in sortedDifficulties.Where(d => d.Difficulty > threshold).OrderByDescending(d => d.Difficulty)) - { - // Add weighted difficulty - screenOverlapDifficulty += Math.Max(0, diffObject.Difficulty - threshold) * weight; - weight *= decay_weight; - } - - return overlap_multiplier * Math.Max(0, screenOverlapDifficulty); - } - public static double EvaluateDifficultyOf(DifficultyHitObject current) - { - if (current.BaseObject is Spinner || current.Index == 0) - return 0; - - double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, ((OsuDifficultyHitObject)current).Density)), 2.5); - - double overlapBonus = EvaluateOverlapDifficultyOf(current) * difficulty; - difficulty += overlapBonus; - - return difficulty; - } - - public static double EvaluateAimingDensityFactorOf(DifficultyHitObject current) - { - double difficulty = ((OsuDifficultyHitObject)current).Density; - - return Math.Max(0, Math.Pow(difficulty, 1.5) - 1); - } - // Returns value from 0 to 1, where 0 is very predictable and 1 is very unpredictable public static double EvaluateInpredictabilityOf(DifficultyHitObject current) { @@ -253,65 +99,8 @@ private static double getVelocityChangeFactor(OsuDifficultyHitObject osuCurrObj, return velocityChangeFactor; } - private static double getTimeNerfFactor(double deltaTime) => Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2); - private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); - - // Finds the overlapness of the last object for which StartTime lower than target - private static double boundBinarySearch(List arr, double target) - { - int low = 0; - int mid; - int high = arr.Count; - - int result = -1; - - while (low < high) - { - mid = low + (high - low) / 2; - - if (arr[mid].HitObject.StartTime >= target) - { - result = mid; - low = mid + 1; - } - else high = mid - 1; - } - - if (result == -1) return 0; - return arr[result].Overlapness; - } } - - public static class ReadingHiddenEvaluator - { - public static double EvaluateDifficultyOf(DifficultyHitObject current) - { - var currObj = (OsuDifficultyHitObject)current; - - double density = ReadingEvaluator.EvaluateDensityOf(current, false); - double preempt = currObj.Preempt / 1000; - - double densityFactor = Math.Pow(density / 6.2, 1.5); - - double invisibilityFactor; - - // AR11+DT and faster = 0 HD pp unless density is big - if (preempt < 0.2) invisibilityFactor = 0; - - // Else accelerating growth until around ART0, then linear, and starting from AR5 is 3 times faster again to buff AR0 +HD - else invisibilityFactor = Math.Min(Math.Pow(preempt * 2.4 - 0.2, 5), Math.Max(preempt, preempt * 3 - 2.4)); - - - double hdDifficulty = invisibilityFactor + densityFactor; - - // Scale by inpredictability slightly - hdDifficulty *= 0.96 + 0.1 * ReadingEvaluator.EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 - - return hdDifficulty; - } - } - public static class ReadingHighAREvaluator { public static double EvaluateDifficultyOf(DifficultyHitObject current, bool applyAdjust = false) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index bf28c14ed80a..c9e87916b3d4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyCalculator : DifficultyCalculator { - public const double DIFFICULTY_MULTIPLIER = 0.0668; + public const double DIFFICULTY_MULTIPLIER = 0.0675; public const double SUM_POWER = 1.1; public const double FL_SUM_POWER = 1.5; public override int Version => 20220902; @@ -43,37 +43,23 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double speedNotes = ((Speed)skills[2]).RelevantNoteCount(); double hiddenFlashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * DIFFICULTY_MULTIPLIER; - double readingLowARRating = Math.Sqrt(skills[4].DifficultyValue()) * DIFFICULTY_MULTIPLIER; - double readingHighARRating = Math.Sqrt(skills[5].DifficultyValue()) * DIFFICULTY_MULTIPLIER; + double readingHighARRating = Math.Sqrt(skills[4].DifficultyValue()) * DIFFICULTY_MULTIPLIER; - double hiddenRating = 0; double flashlightRating = 0; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; - int flIndex = 6; - - double baseReadingHiddenPerformance = 0; - if (mods.Any(h => h is OsuModHidden)) - { - hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * DIFFICULTY_MULTIPLIER; - baseReadingHiddenPerformance = ReadingHidden.DifficultyToPerformance(hiddenRating); - flIndex++; - } - double baseFlashlightPerformance = 0.0; if (mods.Any(h => h is OsuModFlashlight)) { - flashlightRating = Math.Sqrt(skills[flIndex].DifficultyValue()) * DIFFICULTY_MULTIPLIER; + flashlightRating = Math.Sqrt(skills[5].DifficultyValue()) * DIFFICULTY_MULTIPLIER; baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating); } if (mods.Any(m => m is OsuModTouchDevice)) { aimRating = Math.Pow(aimRating, 0.8); - readingLowARRating = Math.Pow(readingLowARRating, 0.9); readingHighARRating = Math.Pow(readingHighARRating, 0.9); - hiddenRating = Math.Pow(hiddenRating, 0.9); flashlightRating = Math.Pow(flashlightRating, 0.8); } @@ -81,9 +67,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat { aimRating *= 0.9; speedRating = 0.0; - readingLowARRating *= 0.95; readingHighARRating *= 0.7; - hiddenRating *= 0.7; flashlightRating *= 0.7; } @@ -91,9 +75,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating); // Cognition - double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating); double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating); - double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SUM_POWER) + Math.Pow(baseReadingHighARPerformance, SUM_POWER), 1.0 / SUM_POWER); + double baseReadingARPerformance = baseReadingHighARPerformance; double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FL_SUM_POWER) + Math.Pow(baseReadingARPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER); @@ -106,7 +89,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat int sliderCount = beatmap.HitObjects.Count(h => h is Slider); int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner); - double cognitionPerformance = baseFlashlightARPerformance + baseReadingHiddenPerformance; + double cognitionPerformance = baseFlashlightARPerformance; double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SUM_POWER) + Math.Pow(baseSpeedPerformance, SUM_POWER), 1.0 / SUM_POWER); // Limit cognition by full memorisation difficulty @@ -131,9 +114,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat AimDifficulty = aimRating, SpeedDifficulty = speedRating, SpeedNoteCount = speedNotes, - ReadingDifficultyLowAR = readingLowARRating, ReadingDifficultyHighAR = readingHighARRating, - HiddenDifficulty = hiddenRating, FlashlightDifficulty = flashlightRating, HiddenFlashlightDifficulty = hiddenFlashlightRating, SliderFactor = sliderFactor, @@ -172,13 +153,9 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo new Aim(mods, false), new Speed(mods), new HiddenFlashlight(mods), - new ReadingLowAR(mods), new ReadingHighAR(mods), }; - if (mods.Any(h => h is OsuModHidden)) - skills.Add(new ReadingHidden(mods)); - if (mods.Any(h => h is OsuModFlashlight)) skills.Add(new Flashlight(mods)); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index fe4cea02c269..8b24ea63862e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -76,24 +76,19 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s // But implementing it is pretty annoying, so I left it "as is" double potentialHiddenFlashlightValue = computeFlashlightValue(score, osuAttributes, true); - double lowARValue = computeReadingLowARValue(score, osuAttributes); double highARValue = computeReadingHighARValue(score, osuAttributes); - double readingARValue = Math.Pow(Math.Pow(lowARValue, power) + Math.Pow(highARValue, power), 1.0 / power); + double readingARValue = highARValue; double flashlightValue = 0; if (score.Mods.Any(h => h is OsuModFlashlight)) flashlightValue = computeFlashlightValue(score, osuAttributes); - double readingHDValue = 0; - if (score.Mods.Any(h => h is OsuModHidden)) - readingHDValue = computeReadingHiddenValue(score, osuAttributes); - // Reduce AR reading bonus if FL is present double flPower = OsuDifficultyCalculator.FL_SUM_POWER; double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, flPower) + Math.Pow(readingARValue, flPower), 1.0 / flPower); - double cognitionValue = flashlightARValue + readingHDValue; + double cognitionValue = flashlightARValue; cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialHiddenFlashlightValue); double accuracyValue = computeAccuracyValue(score, osuAttributes); @@ -129,9 +124,20 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut aimValue *= getComboScalingFactor(attributes); + aimValue *= getComboScalingFactor(attributes); + + double approachRateFactor = 0.0; + if (attributes.ApproachRate < 8.0) + approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate); + + if (score.Mods.Any(h => h is OsuModRelax)) + approachRateFactor = 0.0; + + aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. + if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); - else if (score.Mods.Any(m => m is OsuModTraceable)) + else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); @@ -175,7 +181,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. speedValue *= 1.12; } - else if (score.Mods.Any(m => m is OsuModTraceable)) + else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) { // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); @@ -225,23 +231,12 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. if (score.Mods.Any(m => m is OsuModBlinds)) accuracyValue *= 1.14; + else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable)) + accuracyValue *= 1.08; if (score.Mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; - // Visual indication bonus - double visualBonus = 0.1 * logistic(8.0 - attributes.ApproachRate); - - // Buff if OD is way lower than AR - double ARODDelta = Math.Max(0, attributes.OverallDifficulty - attributes.ApproachRate); - - // This one is goes from 0.0 on delta=0 to 1.0 somewhere around delta=3.4 - double deltaBonus = (1 - Math.Pow(0.95, Math.Pow(ARODDelta, 4))); - - accuracyValue *= 1 + visualBonus * (1 + 2 * deltaBonus); - if (score.Mods.Any(h => h is OsuModHidden || h is OsuModTraceable)) - accuracyValue *= 1 + visualBonus * (1 + deltaBonus); - return accuracyValue; } @@ -277,29 +272,6 @@ public static double ComputePerfectFlashlightValue(double flashlightDifficulty, return flashlightValue; } - private double computeReadingLowARValue(ScoreInfo score, OsuDifficultyAttributes attributes) - { - double rawReading = attributes.ReadingDifficultyLowAR; - - if (score.Mods.Any(m => m is OsuModTouchDevice)) - rawReading = Math.Pow(rawReading, 0.8); - - double readingValue = ReadingLowAR.DifficultyToPerformance(rawReading); - - // 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); - - // Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework. - readingValue *= accuracy * accuracy; - // It is important to also consider accuracy difficulty when doing that. - readingValue *= Math.Pow(0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500, 2); - - return readingValue; - } - private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttributes attributes) { double highARValue = OsuStrainSkill.DifficultyToPerformance(attributes.ReadingDifficultyHighAR); @@ -354,31 +326,6 @@ private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttribute return aimPartValue + speedPartValue; } - private double computeReadingHiddenValue(ScoreInfo score, OsuDifficultyAttributes attributes) - { - if (!score.Mods.Any(h => h is OsuModHidden)) - return 0.0; - - double rawReading = attributes.HiddenDifficulty; - double hiddenValue = ReadingHidden.DifficultyToPerformance(attributes.HiddenDifficulty); - - 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); - - // Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework. - hiddenValue *= accuracy * accuracy; - // It is important to also consider accuracy difficulty when doing that. - hiddenValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; - - return hiddenValue; - } - private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) { // Guess the number of misses + slider breaks from combo @@ -413,6 +360,5 @@ public static double AdjustCognitionPerformance(double cognitionPerformance, dou } 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)); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index bf968e21453e..7b8315806fd4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -103,26 +103,11 @@ public class OsuDifficultyHitObject : DifficultyHitObject /// public double RhythmDifficulty { get; private set; } - /// - /// Density of the object for given preempt. Saved for optimization, density calculation is expensive. - /// - public double Density { get; private set; } - /// /// Predictabiliy of the angle. Gives high values only in exceptionally repetitive patterns. /// public double AnglePredictability { get; private set; } - /// - /// Objects that was visible after the note was hit together with cumulative overlapping difficulty. Saved for optimization to avoid O(x^4) time complexity. - /// - public IList ReadingObjects { get; private set; } - - /// - /// NON ZERO overlap values for each visible object on the moment this object appeared. Key is . Saved for optimization. - /// - public IDictionary OverlapValues { get; private set; } - /// /// Time in ms between appearence of this and moment to click on it. /// @@ -172,128 +157,7 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje AnglePredictability = CalculateAnglePredictability(); - (ReadingObjects, OverlapValues) = getReadingObjects(); - RhythmDifficulty = RhythmEvaluator.EvaluateDifficultyOf(this); - Density = ReadingEvaluator.EvaluateDensityOf(this); - } - - private (IList, IDictionary) getReadingObjects() - { - double totalOverlapnessDifficulty = 0; - double currentTime = DeltaTime; - List historicTimes = []; - List historicAngles = []; - - OsuDifficultyHitObject prevObject = this; - - // The fastest way to do it I've seen so far. Still - one of the slowest parts of the reading calc - var visibleObjects = retrieveCurrentVisibleObjects(this); - - var readingObjects = new List(visibleObjects.Count); - OverlapValues = new Dictionary(); - - //foreach (var loopObj in visibleObjects) - for (int loopIndex = 0; loopIndex < visibleObjects.Count; loopIndex++) - { - var loopObj = visibleObjects[loopIndex]; - - // Overlapness with this object - double currentOverlapness = calculateOverlapness(this, loopObj); - - // Save it for future use. Saving only non-zero to make it faster - if (currentOverlapness > 0) OverlapValues[loopObj.Index] = currentOverlapness; - - if (prevObject.Angle.IsNull()) - { - currentTime += prevObject.DeltaTime; - continue; - } - - // Previous angle because order is reversed; - double angle = (double)prevObject.Angle; - - // Overlapness between current and prev to make streams have 0 buff - double instantOverlapness = 0; - prevObject.OverlapValues?.TryGetValue(loopObj.Index, out instantOverlapness); - - // Nerf overlaps on wide angles - double angleFactor = 1; - angleFactor += (-Math.Cos(angle) + 1) / 2; // =2 for wide angles, =1 for acute angles - instantOverlapness = Math.Min(1, (0.5 + instantOverlapness) * angleFactor); // wide angles are more predictable - - currentOverlapness *= (1 - instantOverlapness) * 2; // wide angles will have close-to-zero buff - - // Control overlap repetitivness - if (currentOverlapness > 0) - { - currentOverlapness *= getOpacitiyMultiplier(loopObj); // Increase stability by using opacity - - double currentMinOverlapness = currentOverlapness; - double cumulativeTimeWithCurrent = currentTime; - - // For every cumulative time with current - for (int i = historicTimes.Count - 1; i >= 0; i--) - { - double cumulativeTimeWithoutCurrent = 0; - - // Get every possible cumulative time without current - for (int j = i; j >= 0; j--) - { - cumulativeTimeWithoutCurrent += historicTimes[j]; - - // Check how similar cumulative times are - double potentialMinOverlapness = currentOverlapness * getTimeDifference(cumulativeTimeWithCurrent, cumulativeTimeWithoutCurrent); - potentialMinOverlapness *= 1 - getAngleSimilarity(angle, historicAngles[j]) * (1 - getTimeDifference(loopObj.StrainTime, prevObject.StrainTime)); - currentMinOverlapness = Math.Min(currentMinOverlapness, potentialMinOverlapness); - - // Check how similar current time with cumulative time - potentialMinOverlapness = currentOverlapness * getTimeDifference(currentTime, cumulativeTimeWithoutCurrent); - potentialMinOverlapness *= 1 - getAngleSimilarity(angle, historicAngles[j]) * (1 - getTimeDifference(loopObj.StrainTime, prevObject.StrainTime)); - currentMinOverlapness = Math.Min(currentMinOverlapness, potentialMinOverlapness); - - // Starting from this point - we will never have better match, so stop searching - if (cumulativeTimeWithoutCurrent >= cumulativeTimeWithCurrent) - break; - } - cumulativeTimeWithCurrent += historicTimes[i]; - } - - currentOverlapness = currentMinOverlapness; - - historicTimes.Add(currentTime); - historicAngles.Add(angle); - - currentTime = prevObject.DeltaTime; - } - else - { - currentTime += prevObject.DeltaTime; - } - - totalOverlapnessDifficulty += currentOverlapness; - - ReadingObject newObj = new ReadingObject(loopObj, totalOverlapnessDifficulty); - readingObjects.Add(newObj); - prevObject = loopObj; - } - - return (readingObjects, OverlapValues); - } - - private double getOpacitiyMultiplier(OsuDifficultyHitObject loopObj) - { - const double threshold = 0.3; - - // Get raw opacity - double opacity = OpacityAt(loopObj.BaseObject.StartTime, false); - - opacity = Math.Min(1, opacity + threshold); // object with opacity 0.7 are still perfectly visible - opacity -= threshold; // return opacity 0 objects back to 0 - opacity /= 1 - threshold; // fix scaling to be 0-1 again - opacity = Math.Sqrt(opacity); // change curve - - return opacity; } private static double getTimeDifference(double timeA, double timeB) @@ -306,62 +170,6 @@ private static double getTimeDifference(double timeA, double timeB) return (Math.Cos((similarity - 0.75) * Math.PI / 0.15) + 1) / 2; // drops from 1 to 0 as similarity increase from 0.75 to 0.9 } - - private static double getAngleSimilarity(double angle1, double angle2) - { - double difference = Math.Abs(angle1 - angle2); - double threeshold = Math.PI / 12; - - if (difference > threeshold) return 0; - return 1 - difference / threeshold; - } - - private static double calculateOverlapness(OsuDifficultyHitObject odho1, OsuDifficultyHitObject odho2) - { - const double area_coef = 0.85; - - double distance = Vector2.Distance(odho1.StackedPosition, odho2.StackedPosition); // Distance func is kinda slow for some reason - double radius = odho1.BaseObject.Radius; - - double distance_sqr = distance * distance; - double radius_sqr = radius * radius; - - if (distance > radius * 2) - return 0; - - double s1 = Math.Acos(distance / (2 * radius)) * radius_sqr; // Area of sector - double s2 = distance * Math.Sqrt(radius_sqr - distance_sqr / 4) / 2; // Area of triangle - - double overlappingAreaNormalized = (s1 - s2) * 2 / (Math.PI * radius_sqr); - - // don't ask me how i get this value, looks oddly similar to PI - 3 - const double stack_distance_ratio = 0.1414213562373; - - double perfectStackBuff = (stack_distance_ratio - distance / radius) / stack_distance_ratio; // scale from 0 on normal stack to 1 on perfect stack - perfectStackBuff = Math.Max(perfectStackBuff, 0); // can't be negative - - return overlappingAreaNormalized * area_coef + perfectStackBuff * (1 - area_coef); - } - - private static List retrieveCurrentVisibleObjects(OsuDifficultyHitObject current) - { - - var visibleObjects = new List(); - - for (int i = 0; i < current.Count; i++) - { - OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i); - - if (hitObject.IsNull() || - hitObject.StartTime < current.StartTime - current.Preempt) - break; - - visibleObjects.Add(hitObject); - } - - return visibleObjects; - } - public double CalculateAnglePredictability() { OsuDifficultyHitObject? prevObj0 = (OsuDifficultyHitObject?)Previous(0); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index ac3e9e64e9bb..964c5543565c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using osu.Framework.Utils; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; @@ -14,109 +12,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { - - public class ReadingLowAR : GraphSkill - { - private readonly List difficulties = new List(); - private double skillMultiplier => 1.26; - private double aimComponentMultiplier => 0.4; - - public ReadingLowAR(Mod[] mods) - : base(mods) - { - } - - private double strainDecayBase => 0.15; - private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); - - private double currentDensityAimStrain = 0; - - public override void Process(DifficultyHitObject current) - { - double densityReadingDifficulty = ReadingEvaluator.EvaluateDifficultyOf(current); - double densityAimingFactor = ReadingEvaluator.EvaluateAimingDensityFactorOf(current); - - currentDensityAimStrain *= strainDecay(current.DeltaTime); - currentDensityAimStrain += densityAimingFactor * AimEvaluator.EvaluateDifficultyOf(current, true) * aimComponentMultiplier; - - double totalDensityDifficulty = (currentDensityAimStrain + densityReadingDifficulty) * skillMultiplier; - - difficulties.Add(totalDensityDifficulty); - - if (current.Index == 0) - CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; - - while (current.StartTime > CurrentSectionEnd) - { - StrainPeaks.Add(CurrentSectionPeak); - CurrentSectionPeak = 0; - CurrentSectionEnd += SectionLength; - } - - CurrentSectionPeak = Math.Max(totalDensityDifficulty, CurrentSectionPeak); - } - - private double reducedNoteCount => 5; - 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); - - List values = peaks.OrderByDescending(d => d).ToList(); - - for (int i = 0; i < Math.Min(values.Count, reducedNoteCount); i++) - { - double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp(i / reducedNoteCount, 0, 1))); - values[i] *= Interpolation.Lerp(reducedNoteBaseline, 1.0, scale); - } - - values = values.OrderByDescending(d => d).ToList(); - - // 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); - } - - return difficulty; - } - 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, 3) * 10.5, Math.Pow(difficulty, 4) * 6.00)); - } - - public class ReadingHidden : Aim - { - public ReadingHidden(Mod[] mods) - : base(mods, false) - { - } - protected new double SkillMultiplier => 7.2; - - protected override double StrainValueAt(DifficultyHitObject current) - { - CurrentStrain *= StrainDecay(current.DeltaTime); - - // We're not using slider aim because we assuming that HD doesn't makes sliders harder (what is not true, but we will ignore this for now) - double hiddenDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false); - hiddenDifficulty *= ReadingHiddenEvaluator.EvaluateDifficultyOf(current); - hiddenDifficulty *= SkillMultiplier; - - CurrentStrain += hiddenDifficulty; - - return CurrentStrain; - } - - public new static double DifficultyToPerformance(double difficulty) => Math.Max( - Math.Max(difficulty * 16, Math.Pow(difficulty, 2) * 10), Math.Pow(difficulty, 3) * 4); - } - - public class ReadingHighAR : GraphSkill + public class ReadingHighAR : Skill { private const double component_multiplier = 0.135; @@ -141,25 +37,6 @@ public override void Process(DifficultyHitObject current) if (current.BaseObject is not Spinner) objectsCount++; - - double power = OsuDifficultyCalculator.SUM_POWER; - double mergedDifficulty = Math.Pow( - Math.Pow(aimComponent.CurrentSectionPeak, power) + - Math.Pow(speedComponent.CurrentSectionPeak, power), 1.0 / power); - - difficulties.Add(mergedDifficulty); - - if (current.Index == 0) - CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; - - while (current.StartTime > CurrentSectionEnd) - { - StrainPeaks.Add(CurrentSectionPeak); - CurrentSectionPeak = 0; - CurrentSectionEnd += SectionLength; - } - - CurrentSectionPeak = Math.Max(mergedDifficulty, CurrentSectionPeak); } public override double DifficultyValue() { diff --git a/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs deleted file mode 100644 index 953abd61fe33..000000000000 --- a/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using System.Linq; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Difficulty.Skills -{ - /// - /// A abstract skill with available per objet difficulty. - /// - /// - /// This class should be considered a "processing" class and not persisted. - /// - public abstract class GraphSkill : Skill - { - protected GraphSkill(Mod[] mods) - : base(mods) - { - } - - /// - /// The length of each section. - /// - protected virtual int SectionLength => 400; - - public double CurrentSectionPeak { get; protected set; } // We also keep track of the peak level in the current section. - - protected double CurrentSectionEnd; - - protected readonly List StrainPeaks = new List(); - - /// - /// Returns a live enumerable of the difficulties - /// - public virtual IEnumerable GetCurrentStrainPeaks() => StrainPeaks.Append(CurrentSectionPeak); - } -} diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index ace40b07b2a3..b07e8399c024 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -2,6 +2,7 @@ // 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; @@ -12,13 +13,24 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// Used to processes strain values of s, keep track of strain levels caused by the processed objects /// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects. /// - public abstract class StrainSkill : GraphSkill + public abstract class StrainSkill : Skill { /// /// The weight by which each strain value decays. /// protected virtual double DecayWeight => 0.9; + /// + /// The length of each strain section. + /// + protected virtual int SectionLength => 400; + + private double currentSectionPeak; // We also keep track of the peak strain level in the current section. + + private double currentSectionEnd; + + private readonly List strainPeaks = new List(); + protected StrainSkill(Mod[] mods) : base(mods) { @@ -36,16 +48,16 @@ public sealed override void Process(DifficultyHitObject current) { // The first object doesn't generate a strain, so we begin with an incremented section end if (current.Index == 0) - CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; + currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; - while (current.StartTime > CurrentSectionEnd) + while (current.StartTime > currentSectionEnd) { saveCurrentPeak(); - startNewSectionFrom(CurrentSectionEnd, current); - CurrentSectionEnd += SectionLength; + startNewSectionFrom(currentSectionEnd, current); + currentSectionEnd += SectionLength; } - CurrentSectionPeak = Math.Max(StrainValueAt(current), CurrentSectionPeak); + currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak); } /// @@ -53,7 +65,7 @@ public sealed override void Process(DifficultyHitObject current) /// private void saveCurrentPeak() { - StrainPeaks.Add(CurrentSectionPeak); + strainPeaks.Add(currentSectionPeak); } /// @@ -65,7 +77,7 @@ private void startNewSectionFrom(double time, DifficultyHitObject current) { // The maximum strain of the new section is not zero by default // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. - CurrentSectionPeak = CalculateInitialStrain(time, current); + currentSectionPeak = CalculateInitialStrain(time, current); } /// @@ -76,6 +88,12 @@ private void startNewSectionFrom(double time, DifficultyHitObject current) /// The peak strain. protected abstract double CalculateInitialStrain(double time, DifficultyHitObject current); + /// + /// Returns a live enumerable of the peak strains for each section of the beatmap, + /// including the peak of the current section. + /// + public IEnumerable GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak); + /// /// Returns the calculated difficulty value representing all s that have been processed up to this point. /// From b65230443cd253e9856abe4676dc759fa2736899 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 6 May 2024 01:53:25 +0300 Subject: [PATCH 94/96] more clean-up --- .../Difficulty/OsuPerformanceCalculator.cs | 2 -- .../Preprocessing/OsuDifficultyHitObject.cs | 18 ++---------------- .../Preprocessing/DifficultyHitObject.cs | 5 ----- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 8b24ea63862e..00c05f84303e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -124,8 +124,6 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut aimValue *= getComboScalingFactor(attributes); - aimValue *= getComboScalingFactor(attributes); - double approachRateFactor = 0.0; if (attributes.ApproachRate < 8.0) approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 7b8315806fd4..b1848bebcfe9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -33,11 +33,6 @@ public class OsuDifficultyHitObject : DifficultyHitObject /// public readonly double StrainTime; - /// - /// Saved version of to decrease overhead. - /// - public readonly Vector2 StackedPosition; - /// /// Normalised distance from the "lazy" end position of the previous to the start position of this . /// @@ -80,19 +75,12 @@ public class OsuDifficultyHitObject : DifficultyHitObject public double TravelTime { get; private set; } /// - /// Absolute angle the player has to take to hit this . + /// Angle the player has to take to hit this . /// Calculated as the angle between the circles (current-2, current-1, current). /// Ranges from 0 to PI /// public double? Angle { get; private set; } - /// - /// Signed version of the Angle. - /// Potentially should be used for more accurate angle bonuses - /// Ranges from -PI to PI - /// - public double? AngleSigned { get; private set; } - /// /// Retrieves the full hit window for a Great . /// @@ -135,7 +123,6 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje this.lastObject = (OsuHitObject)lastObject; this.lastLastObject = lastLastObject as OsuHitObject; - StackedPosition = currObject.StackedPosition; Preempt = BaseObject.TimePreempt / clockRate; FollowLineTime = 800 / clockRate; // 800ms is follow line appear time FollowLineTime *= (currObject.NewCombo ? 0 : 1); // no follow lines when NC @@ -345,8 +332,7 @@ private void setDistances(double clockRate) float dot = Vector2.Dot(v1, v2); float det = v1.X * v2.Y - v1.Y * v2.X; - AngleSigned = Math.Atan2(det, dot); - Angle = Math.Abs((double)AngleSigned); + Angle = Math.Abs(Math.Atan2(det, dot)); } } diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index cd9dd3572c3b..9785865192bd 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -15,11 +15,6 @@ public class DifficultyHitObject { private readonly IReadOnlyList difficultyHitObjects; - /// - /// The index of this in the list of all s. - /// - public int Count => difficultyHitObjects.Count; - /// /// The index of this in the list of all s. /// From 242ca611b93c4d23933b61720fdc93dda4915179 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 6 May 2024 01:54:53 +0300 Subject: [PATCH 95/96] removed attributes --- .../Difficulty/OsuDifficultyAttributes.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 4ee92c7b8f72..35a488906dcc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -32,30 +32,12 @@ public class OsuDifficultyAttributes : DifficultyAttributes [JsonProperty("speed_note_count")] public double SpeedNoteCount { get; set; } - /// - /// The difficulty corresponding to the reading skill. Low AR branch. - /// - [JsonProperty("reading_low_ar_difficulty")] - public double ReadingDifficultyLowAR { get; set; } - /// /// The difficulty corresponding to the reading skill. High AR branch. /// [JsonProperty("reading_high_ar_difficulty")] public double ReadingDifficultyHighAR { get; set; } - /// - /// The difficulty corresponding to the reading skill. Sliders branch. - /// - [JsonProperty("reading_sliders_difficulty")] - public double ReadingDifficultySliders { get; set; } - - /// - /// The difficulty corresponding to the reading skill. Hidden mod branch. - /// - [JsonProperty("reading_hidden_difficulty")] - public double HiddenDifficulty { get; set; } - /// /// The difficulty corresponding to the flashlight skill. /// From 9b14156d3166213a7e0217d674d5f451a58e69d6 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 18 May 2024 13:32:07 +0300 Subject: [PATCH 96/96] refactoring and balancing --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 7 ++-- .../Difficulty/OsuDifficultyCalculator.cs | 7 ++-- .../Difficulty/OsuPerformanceCalculator.cs | 15 ++++----- .../Difficulty/Skills/Aim.cs | 1 - .../Difficulty/Skills/Reading.cs | 33 +++++++++++++------ 5 files changed, 37 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index 750cb8ef3237..e8f1781d02d9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -122,8 +122,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool appl return result; } - // High AR curve - // https://www.desmos.com/calculator/srzbeumngi + // High AR curve (this curve is without Math.Pow(value, 2)) + // https://www.desmos.com/calculator/xuuwd77cbq public static double GetDifficulty(double preempt) { // Get preempt in seconds @@ -136,8 +136,7 @@ public static double GetDifficulty(double preempt) value = Math.Exp(9.07583 - 80.0 * preempt / 3); // The power is 2 times higher to compensate sqrt in high AR skill - // EDIT: looks like AR11 getting a bit overnerfed in comparison to other ARs, so i will increase the difference - return Math.Pow(value, 2.2); + return Math.Pow(value, 2); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index c9e87916b3d4..ab06b92732f6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -41,6 +41,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double speedNotes = ((Speed)skills[2]).RelevantNoteCount(); + double hiddenFlashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * DIFFICULTY_MULTIPLIER; double readingHighARRating = Math.Sqrt(skills[4].DifficultyValue()) * DIFFICULTY_MULTIPLIER; @@ -92,9 +93,9 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double cognitionPerformance = baseFlashlightARPerformance; double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SUM_POWER) + Math.Pow(baseSpeedPerformance, SUM_POWER), 1.0 / SUM_POWER); - // Limit cognition by full memorisation difficulty - double maxHiddenFlashlightPerformance = OsuPerformanceCalculator.ComputePerfectFlashlightValue(hiddenFlashlightRating, hitCirclesCount + sliderCount); - cognitionPerformance = OsuPerformanceCalculator.AdjustCognitionPerformance(cognitionPerformance, mechanicalPerformance, maxHiddenFlashlightPerformance); + // Limit cognition by full memorisation difficulty, what is assumed to be mechanicalPerformance + hiddenFlashlightPerformance + double hiddenFlashlightPerformance = OsuPerformanceCalculator.ComputePerfectFlashlightValue(hiddenFlashlightRating, hitCirclesCount + sliderCount); + cognitionPerformance = OsuPerformanceCalculator.AdjustCognitionPerformance(cognitionPerformance, mechanicalPerformance, hiddenFlashlightPerformance); double basePerformance = mechanicalPerformance + cognitionPerformance; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 00c05f84303e..a1235c4f8539 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -70,10 +70,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s // Cognition // Get HDFL value for capping reading performance - // In theory stuff like AR13, AR13 +HD and AR-INF +HD should use this values - // While AR-INF without HD shoud use normal flashlight values - // Because in first case you're clicking air, while in AR-INF case you're see the notes - // But implementing it is pretty annoying, so I left it "as is" + // In the future consider separating "all notes all invisible" and "full-memory but notes are visible" case double potentialHiddenFlashlightValue = computeFlashlightValue(score, osuAttributes, true); double highARValue = computeReadingHighARValue(score, osuAttributes); @@ -240,7 +237,7 @@ private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes att private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes, bool alwaysUseHD = false) { - double flashlightValue = Math.Pow(alwaysUseHD ? attributes.HiddenFlashlightDifficulty : attributes.FlashlightDifficulty, 2.0) * 25.0; + double flashlightValue = Flashlight.DifficultyToPerformance(alwaysUseHD ? attributes.HiddenFlashlightDifficulty : attributes.FlashlightDifficulty); // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) @@ -344,11 +341,13 @@ private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) 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; - // Adjusts cognition performance accounting for full-memory - public static double AdjustCognitionPerformance(double cognitionPerformance, double mechanicalPerformance, double flaslightPerformance) + // 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 + public static double AdjustCognitionPerformance(double cognitionPerformance, double mechanicalPerformance, double flashlightPerformance) { // Assuming that less than 25 mechanical pp is not worthy for memory - double capPerformance = mechanicalPerformance + flaslightPerformance + 25; + double capPerformance = mechanicalPerformance + flashlightPerformance + 25; double ratio = cognitionPerformance / capPerformance; if (ratio > 50) return capPerformance; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 6929095f218a..289c7cf2df98 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -1,7 +1,6 @@ // 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 osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 964c5543565c..5951c012175a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class ReadingHighAR : Skill { - private const double component_multiplier = 0.135; - private const double component_default_value_multiplier = 60; + private const double skill_multiplier = 0.13727; + private const double component_default_value_multiplier = 445; public ReadingHighAR(Mod[] mods) : base(mods) { @@ -28,7 +28,9 @@ public ReadingHighAR(Mod[] mods) private HighARSpeedComponent speedComponent; private readonly List difficulties = new List(); + private int objectsCount = 0; + private double objectsPreemptSum = 0; public override void Process(DifficultyHitObject current) { @@ -36,13 +38,16 @@ public override void Process(DifficultyHitObject current) speedComponent.Process(current); if (current.BaseObject is not Spinner) + { objectsCount++; + objectsPreemptSum += ((OsuDifficultyHitObject)current).Preempt; + } } public override double DifficultyValue() { // Simulating summing to get the most correct value possible - double aimValue = Math.Sqrt(aimComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; - double speedValue = Math.Sqrt(speedComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; + double aimValue = Math.Sqrt(aimComponent.DifficultyValue() * skill_multiplier) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; + double speedValue = Math.Sqrt(speedComponent.DifficultyValue() * skill_multiplier) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER; double aimPerformance = OsuStrainSkill.DifficultyToPerformance(aimValue); double speedPerformance = OsuStrainSkill.DifficultyToPerformance(speedValue); @@ -50,13 +55,25 @@ public override double DifficultyValue() double power = OsuDifficultyCalculator.SUM_POWER; double totalPerformance = Math.Pow(Math.Pow(aimPerformance, power) + Math.Pow(speedPerformance, power), 1.0 / power); - // Length bonus is in SR to not inflate Star Rating short AR11 maps + // Length bonus is in SR to not inflate Star Rating of short AR11 maps double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount); - totalPerformance *= Math.Pow(lengthBonus, 4); // make it bypass sqrt + + // Get average preempt of objects + double averagePreempt = objectsPreemptSum / objectsCount / 1000; + + // Increase length bonus for long maps with very high AR + // https://www.desmos.com/calculator/wz9wckqgzu + double lengthBonusPower = 2 + 2 * Math.Pow(0.1, Math.Pow(2.3 * averagePreempt, 8)); + + // Be sure that increasing AR won't decrease pp + if (lengthBonus < 1) lengthBonusPower = 2; + + totalPerformance *= Math.Pow(lengthBonus, lengthBonusPower); double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance); double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); + // Sqrt value to make difficulty depend less on mechanical difficulty return 53.2 * Math.Sqrt(difficultyValue); } @@ -67,8 +84,6 @@ public HighARAimComponent(Mod[] mods) { } - protected new double SkillMultiplier => base.SkillMultiplier * component_multiplier; - protected override double StrainValueAt(DifficultyHitObject current) { CurrentStrain *= StrainDecay(current.DeltaTime); @@ -89,8 +104,6 @@ public HighARSpeedComponent(Mod[] mods) { } - protected new double SkillMultiplier => base.SkillMultiplier * component_multiplier; - protected override double StrainValueAt(DifficultyHitObject current) { OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current;