From ffeb46af9b9d4c4d2b0013f107404c717a5f2258 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 8 Jan 2024 14:53:38 +0200 Subject: [PATCH] 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 3e07ad4185..9e80cc1732 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.Difficulty.Preprocessing; 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 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { 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 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators var clockRateEstimate = current.BaseObject.StartTime / currObj.StartTime; double pastObjectDifficultyInfluence = 1.0; + double screenOverlapDifficulty = 0; foreach (var loopObj in retrievePastVisibleObjects(currObj)) { @@ -39,8 +45,23 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators 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 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators (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 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators 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 1fb38d47a8..ec38da793d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -104,6 +104,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty 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 @@ namespace osu.Game.Rulesets.Osu.Difficulty 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 @@ namespace osu.Game.Rulesets.Osu.Difficulty // 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 @@ namespace osu.Game.Rulesets.Osu.Difficulty // 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 8b31e53025..ce9ba96faa 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 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// 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 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing } 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 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing return pos; } + + public struct OverlapObject + { + public OsuDifficultyHitObject HitObject; + public double Overlapness; + + public OverlapObject(OsuDifficultyHitObject hitObject, double overlapness) + { + HitObject = hitObject; + Overlapness = overlapness; + } + } } }