From 5409017f3c097a2c7150f08f43bb51bffd90d9cf Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Sun, 21 Apr 2024 15:32:07 +0800 Subject: [PATCH 01/21] add PreviousMaxCombo and CurrentMaxCombo to OsuDifficultyHitObject --- .../Preprocessing/OsuDifficultyHitObject.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 0e537632b164..073169978625 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -83,6 +83,16 @@ public class OsuDifficultyHitObject : DifficultyHitObject /// public double HitWindowGreat { get; private set; } + /// + /// The maximum combo played before this . + /// + public int PreviousMaxCombo { get; private set; } + + /// + /// The maximum combo played after this . + /// + public int CurrentMaxCombo { get; private set; } + private readonly OsuHitObject? lastLastObject; private readonly OsuHitObject lastObject; @@ -105,6 +115,13 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje } setDistances(clockRate); + + if (index > 0) + PreviousMaxCombo = ((OsuDifficultyHitObject)Previous(0)).CurrentMaxCombo; + else + PreviousMaxCombo = getObjectCombo(lastObject); + + CurrentMaxCombo = PreviousMaxCombo + getObjectCombo(hitObject); } public double OpacityAt(double time, bool hidden) @@ -327,5 +344,18 @@ private Vector2 getEndCursorPosition(OsuHitObject hitObject) return pos; } + + private int getObjectCombo(HitObject hitObject) + { + if (hitObject is Slider slider) + { + if (slider.NestedHitObjects[1] is SliderRepeat) + return slider.RepeatCount + 2; + else + return slider.NestedHitObjects.Count; + } + + return 1; + } } } From 6f110a800227ac0227df6ef01dc1019cff5a2a59 Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Thu, 25 Apr 2024 13:06:41 +0800 Subject: [PATCH 02/21] consider jump from lazy end position for sliders --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 5cb5a8f93417..246ae30c2165 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -5,6 +5,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { @@ -54,13 +55,20 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd if (!(currentObj.BaseObject is Spinner)) { - double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length; + double pixelJumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length; + + // Consider the jump from the lazy end position for sliders. + if (currentHitObject is Slider currentSlider) + { + Vector2 lazyEndPosition = currentSlider.LazyEndPosition ?? currentSlider.StackedPosition; + pixelJumpDistance = Math.Min(pixelJumpDistance, (osuHitObject.StackedPosition - lazyEndPosition).Length); + } cumulativeStrainTime += lastObj.StrainTime; // We want to nerf objects that can be easily seen within the Flashlight circle radius. if (i == 0) - smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); + smallDistNerf = Math.Min(1.0, pixelJumpDistance / 75.0); // We also want to nerf stacks so that only the first object of the stack is accounted for. double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); @@ -68,7 +76,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Bonus based on how visible the object is. double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, hidden)); - result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; + result += stackNerf * opacityBonus * scalingFactor * pixelJumpDistance / cumulativeStrainTime; if (currentObj.Angle != null && osuCurrent.Angle != null) { From cd1bebd57e15726c0162134e8b5ed5ebd22fb066 Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:13:53 +0800 Subject: [PATCH 03/21] adjust accuracy value for hd and hdfl --- .../Difficulty/OsuPerformanceCalculator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 18a4b8be0caf..c965b36a4c58 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -212,12 +212,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; + // Use different multiplier when adding hidden or traceable to flashlight. + else if (score.Mods.Any(m => m is OsuModFlashlight)) + accuracyValue *= score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable) ? 1.12 : 1.08; 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; - return accuracyValue; } From d89f5584393dd332b95a4f288a2eef69e47432bf Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:26:00 +0800 Subject: [PATCH 04/21] fix spinners not increasing cumulative strain time --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 5cb5a8f93417..9d05f0b0742a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -52,12 +52,12 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd var currentObj = (OsuDifficultyHitObject)current.Previous(i); var currentHitObject = (OsuHitObject)(currentObj.BaseObject); + cumulativeStrainTime += lastObj.StrainTime; + if (!(currentObj.BaseObject is Spinner)) { double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length; - cumulativeStrainTime += lastObj.StrainTime; - // We want to nerf objects that can be easily seen within the Flashlight circle radius. if (i == 0) smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); From 6fd3d58a63a4e700554538990123fd65ed8b4ccc Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:17:35 +0800 Subject: [PATCH 05/21] new flashlight slider difficulty calculation --- .../Evaluators/FlashlightEvaluator.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 5cb5a8f93417..797c710fa06f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -14,7 +14,8 @@ public static class FlashlightEvaluator private const double hidden_bonus = 0.2; private const double min_velocity = 0.5; - private const double slider_multiplier = 1.3; + private const double max_velocity = 1.5; + private const double slider_multiplier = 0.3; private const double min_angle_multiplier = 0.2; @@ -97,18 +98,21 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Invert the scaling factor to determine the true travel distance independent of circle size. double pixelTravelDistance = osuSlider.LazyTravelDistance / scalingFactor; - // Reward sliders based on velocity. - sliderBonus = Math.Pow(Math.Max(0.0, pixelTravelDistance / osuCurrent.TravelTime - min_velocity), 0.5); + // Reward sliders based on cursor velocity. + sliderBonus = Math.Log(pixelTravelDistance / osuCurrent.TravelTime + 1); - // Longer sliders require more memorisation. - sliderBonus *= pixelTravelDistance; + // More cursor movement requires more memorisation. + sliderBonus *= osuSlider.LazyTravelDistance; - // Nerf sliders with repeats, as less memorisation is required. - if (osuSlider.RepeatCount > 0) - sliderBonus /= (osuSlider.RepeatCount + 1); + // Nerf slow slider velocity. + double sliderVelocity = osuSlider.Distance / osuCurrent.TravelTime; + sliderBonus *= Math.Clamp((sliderVelocity - min_velocity) / (max_velocity - min_velocity), 0, 1); + + // Nerf sliders the more repeats they have, as less memorisation is required. + sliderBonus /= 0.75 * osuSlider.RepeatCount + 1; } - result += sliderBonus * slider_multiplier; + result += Math.Pow(sliderBonus, 1.2) * slider_multiplier; return result; } From bc96fd2054f612d1f741309e6b43140a5b0c1805 Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Tue, 14 May 2024 20:35:33 +0800 Subject: [PATCH 06/21] account for visible radius in flashlight evaluator --- .../Evaluators/FlashlightEvaluator.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 5cb5a8f93417..ab2c7a9ee27f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -12,6 +12,7 @@ public static class FlashlightEvaluator { private const double max_opacity_bonus = 0.4; private const double hidden_bonus = 0.2; + private const double flashlight_padding = 80; private const double min_velocity = 0.5; private const double slider_multiplier = 1.3; @@ -60,7 +61,10 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // We want to nerf objects that can be easily seen within the Flashlight circle radius. if (i == 0) - smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); + { + float flashlightRadius = 200 * getComboScaleFor(osuCurrent.PreviousMaxCombo); + smallDistNerf = Math.Min(1.0, jumpDistance / (flashlightRadius + osuHitObject.Radius - flashlight_padding)); + } // We also want to nerf stacks so that only the first object of the stack is accounted for. double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); @@ -112,5 +116,16 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd return result; } + + private static float getComboScaleFor(int combo) + { + // Taken from ModFlashlight. + if (combo >= 200) + return 0.625f; + if (combo >= 100) + return 0.8125f; + + return 1.0f; + } } } From 5bfc6c07c78249f200ab6fe5992a020865c39861 Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Mon, 20 May 2024 17:16:29 +0800 Subject: [PATCH 07/21] account for misses causing increased flashlight radius in performance --- .../Difficulty/OsuPerformanceCalculator.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 18a4b8be0caf..fd62b119988b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -238,6 +238,17 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) + (totalHits > 200 ? 0.2 * Math.Min(1.0, (totalHits - 200) / 200.0) : 0.0); + // Account for scores where the flashlight radius is increased due to misses. + int missingCombo = attributes.MaxCombo - scoreMaxCombo; + double missingComboPercentage = (double)missingCombo / attributes.MaxCombo; + double averageMissingComboLength = Math.Max(missingCombo, 1) / Math.Max(effectiveMissCount, 1); + + double maximumSizePercentage = Math.Clamp(averageMissingComboLength, 0, 100) / averageMissingComboLength * missingComboPercentage; + double mediumSizePercentage = Math.Clamp(averageMissingComboLength - 100, 0, 100) / averageMissingComboLength * missingComboPercentage; + double minimumSizePercentage = 1.0 - mediumSizePercentage - maximumSizePercentage; + + flashlightValue *= minimumSizePercentage + 0.6 * mediumSizePercentage + 0.2 * maximumSizePercentage; + // Scale the flashlight value with accuracy _slightly_. flashlightValue *= 0.5 + accuracy / 2.0; // It is important to also consider accuracy difficulty when doing that. From 3d038cea0d1c5279759236becb094fe833dbd61b Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Mon, 20 May 2024 17:17:34 +0800 Subject: [PATCH 08/21] new flashlight length bonus based on max combo --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index fd62b119988b..ab1702e2d89f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -234,9 +234,8 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a flashlightValue *= getComboScalingFactor(attributes); - // Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius. - flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) + - (totalHits > 200 ? 0.2 * Math.Min(1.0, (totalHits - 200) / 200.0) : 0.0); + // Account for shorter maps having more time played at a larger flashlight radius. + flashlightValue *= Math.Min(1, 0.7 + 0.1 * Math.Max(0, Math.Log(attributes.MaxCombo / 100.0)) + 0.1 * Math.Max(0, Math.Log(attributes.MaxCombo / 200.0))); // Account for scores where the flashlight radius is increased due to misses. int missingCombo = attributes.MaxCombo - scoreMaxCombo; From baacb9b4c4b7e9b9e2ba350a50ee27b91e3b8fa8 Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Mon, 20 May 2024 18:09:28 +0800 Subject: [PATCH 09/21] dont assume worst case scenario for averageMissingComboLength --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index ab1702e2d89f..ac994e2a21fe 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -240,7 +240,9 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a // Account for scores where the flashlight radius is increased due to misses. int missingCombo = attributes.MaxCombo - scoreMaxCombo; double missingComboPercentage = (double)missingCombo / attributes.MaxCombo; - double averageMissingComboLength = Math.Max(missingCombo, 1) / Math.Max(effectiveMissCount, 1); + + // For balancing purposes, assume the player made 3 misses for every memorisation mistake. + double averageMissingComboLength = Math.Max(missingCombo, 1) / Math.Max(effectiveMissCount / 3, 1); double maximumSizePercentage = Math.Clamp(averageMissingComboLength, 0, 100) / averageMissingComboLength * missingComboPercentage; double mediumSizePercentage = Math.Clamp(averageMissingComboLength - 100, 0, 100) / averageMissingComboLength * missingComboPercentage; From 23f3732e42060c8429551517e5d9fb02b9ac4aa5 Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Mon, 20 May 2024 18:31:18 +0800 Subject: [PATCH 10/21] convert into ?: expression --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 073169978625..8f0b23d5f211 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -116,11 +116,7 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObje setDistances(clockRate); - if (index > 0) - PreviousMaxCombo = ((OsuDifficultyHitObject)Previous(0)).CurrentMaxCombo; - else - PreviousMaxCombo = getObjectCombo(lastObject); - + PreviousMaxCombo = index > 0 ? ((OsuDifficultyHitObject)Previous(0)).CurrentMaxCombo : getObjectCombo(lastObject); CurrentMaxCombo = PreviousMaxCombo + getObjectCombo(hitObject); } From db68fd731b994c33c7f3770a56da66b79760bf2f Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Tue, 21 May 2024 18:57:26 +0800 Subject: [PATCH 11/21] new flashlight distance nerfs --- .../Evaluators/FlashlightEvaluator.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index ab2c7a9ee27f..c8fd07fdcf31 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -56,23 +56,26 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd if (!(currentObj.BaseObject is Spinner)) { double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length; + float flashlightRadius = 200 * getComboScaleFor(currentObj.CurrentMaxCombo); + double objectOpacity = osuCurrent.OpacityAt(currentHitObject.StartTime, hidden); cumulativeStrainTime += lastObj.StrainTime; - // We want to nerf objects that can be easily seen within the Flashlight circle radius. + // Apply a nerf based on the visibility from the current object. + double distanceNerf = Math.Min(1.0, jumpDistance / (flashlightRadius + osuHitObject.Radius - flashlight_padding)); + double visibilityNerf = 1.0 - objectOpacity * (1.0 - distanceNerf); + + // Jumps within the visible Flashlight radius should be nerfed. if (i == 0) - { - float flashlightRadius = 200 * getComboScaleFor(osuCurrent.PreviousMaxCombo); - smallDistNerf = Math.Min(1.0, jumpDistance / (flashlightRadius + osuHitObject.Radius - flashlight_padding)); - } + smallDistNerf = Math.Min(1.0, jumpDistance / (flashlightRadius - 50)); - // We also want to nerf stacks so that only the first object of the stack is accounted for. + // Nerf stacks so that only the first object of the stack is accounted for. double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); - // Bonus based on how visible the object is. - double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, hidden)); + // Bonus based on object opacity. + double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - objectOpacity); - result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; + result += visibilityNerf * stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime; if (currentObj.Angle != null && osuCurrent.Angle != null) { From d5d295f6634b04ecfb54e554a7e4b52d28dcbecd Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Tue, 21 May 2024 19:00:26 +0800 Subject: [PATCH 12/21] flashlight cleanups --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index c8fd07fdcf31..70fd1af91588 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -12,7 +12,6 @@ public static class FlashlightEvaluator { private const double max_opacity_bonus = 0.4; private const double hidden_bonus = 0.2; - private const double flashlight_padding = 80; private const double min_velocity = 0.5; private const double slider_multiplier = 1.3; @@ -56,13 +55,13 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd if (!(currentObj.BaseObject is Spinner)) { double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length; - float flashlightRadius = 200 * getComboScaleFor(currentObj.CurrentMaxCombo); + double flashlightRadius = getComboScaleFor(currentObj.CurrentMaxCombo); double objectOpacity = osuCurrent.OpacityAt(currentHitObject.StartTime, hidden); cumulativeStrainTime += lastObj.StrainTime; // Apply a nerf based on the visibility from the current object. - double distanceNerf = Math.Min(1.0, jumpDistance / (flashlightRadius + osuHitObject.Radius - flashlight_padding)); + double distanceNerf = Math.Min(1.0, jumpDistance / (flashlightRadius + osuHitObject.Radius - 80)); double visibilityNerf = 1.0 - objectOpacity * (1.0 - distanceNerf); // Jumps within the visible Flashlight radius should be nerfed. @@ -120,15 +119,15 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd return result; } - private static float getComboScaleFor(int combo) + private static double getComboScaleFor(int combo) { // Taken from ModFlashlight. if (combo >= 200) - return 0.625f; + return 125.0; if (combo >= 100) - return 0.8125f; + return 162.5; - return 1.0f; + return 200.0; } } } From 3aa05762590f22c2c20b370c5e1707c367a0c72c Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Tue, 21 May 2024 19:12:00 +0800 Subject: [PATCH 13/21] buff flashlight difficulty constants --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 007cd977e599..b7ed1a33283e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -66,7 +66,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseFlashlightPerformance = 0.0; if (mods.Any(h => h is OsuModFlashlight)) - baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; + baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 28.0; double basePerformance = Math.Pow( diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 894fbae7db7d..5701febee6c7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -226,7 +226,7 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a if (!score.Mods.Any(h => h is OsuModFlashlight)) return 0.0; - double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0; + double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 28.0; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) From 831485784031d68628ae6115835b24e8d6f6d922 Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Tue, 21 May 2024 21:31:42 +0800 Subject: [PATCH 14/21] tweak flashlight constants some more --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index bb6a4c9ff271..a320c7cd6f28 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -75,7 +75,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Jumps within the visible Flashlight radius should be nerfed. if (i == 0) - smallDistNerf = Math.Min(1.0, pixelJumpDistance / (flashlightRadius - 50)); + smallDistNerf = Math.Min(1.0, pixelJumpDistance / (flashlightRadius - 45)); // Nerf stacks so that only the first object of the stack is accounted for. double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index b7ed1a33283e..cb4606342897 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -66,7 +66,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double baseFlashlightPerformance = 0.0; if (mods.Any(h => h is OsuModFlashlight)) - baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 28.0; + baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 28.727; double basePerformance = Math.Pow( diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 5701febee6c7..4ed6dc55f51d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -226,7 +226,7 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a if (!score.Mods.Any(h => h is OsuModFlashlight)) return 0.0; - double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 28.0; + double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 28.727; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) From e06e57c54a5d1bded1a3bb617958a3a54d0442d7 Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Tue, 28 May 2024 14:48:53 +0800 Subject: [PATCH 15/21] better variable names --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index a320c7cd6f28..9d000cf07d8e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -58,7 +58,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd if (!(currentObj.BaseObject is Spinner)) { - double pixelJumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length; + double pixelDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length; double flashlightRadius = getComboScaleFor(currentObj.CurrentMaxCombo); double objectOpacity = osuCurrent.OpacityAt(currentHitObject.StartTime, hidden); @@ -66,16 +66,16 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd if (currentHitObject is Slider currentSlider) { Vector2 lazyEndPosition = currentSlider.LazyEndPosition ?? currentSlider.StackedPosition; - pixelJumpDistance = Math.Min(pixelJumpDistance, (osuHitObject.StackedPosition - lazyEndPosition).Length); + pixelDistance = Math.Min(pixelDistance, (osuHitObject.StackedPosition - lazyEndPosition).Length); } // Apply a nerf based on the visibility from the current object. - double distanceNerf = Math.Min(1.0, pixelJumpDistance / (flashlightRadius + osuHitObject.Radius - 80)); - double visibilityNerf = 1.0 - objectOpacity * (1.0 - distanceNerf); + double radiusVisibility = Math.Min(1.0, pixelDistance / (flashlightRadius + osuHitObject.Radius - 80)); + double visibilityNerf = 1.0 - objectOpacity * (1.0 - radiusVisibility); - // Jumps within the visible Flashlight radius should be nerfed. + // Small jumps within the visible Flashlight radius should be nerfed. if (i == 0) - smallDistNerf = Math.Min(1.0, pixelJumpDistance / (flashlightRadius - 45)); + smallDistNerf = Math.Min(1.0, pixelDistance / (flashlightRadius - 45)); // Nerf stacks so that only the first object of the stack is accounted for. double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); @@ -83,7 +83,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Bonus based on object opacity. double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - objectOpacity); - result += visibilityNerf * stackNerf * opacityBonus * scalingFactor * pixelJumpDistance / cumulativeStrainTime; + result += visibilityNerf * stackNerf * opacityBonus * scalingFactor * pixelDistance / cumulativeStrainTime; if (currentObj.Angle != null && osuCurrent.Angle != null) { From a0c45bb9d8cbfac4d0d72826ac85d479e27bacee Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Thu, 7 Nov 2024 16:15:32 +0000 Subject: [PATCH 16/21] support custom flashlight settings --- .../Evaluators/FlashlightEvaluator.cs | 24 +++++++++++++------ .../Difficulty/Skills/Flashlight.cs | 4 +++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 9d000cf07d8e..6ddd30c09752 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -4,6 +4,7 @@ using System; 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; @@ -30,7 +31,7 @@ public static class FlashlightEvaluator /// and whether the hidden mod is enabled. /// /// - public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden) + public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden, OsuModFlashlight osuModFlashlight) { if (current.BaseObject is Spinner) return 0; @@ -59,7 +60,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd if (!(currentObj.BaseObject is Spinner)) { double pixelDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length; - double flashlightRadius = getComboScaleFor(currentObj.CurrentMaxCombo); + double flashlightRadius = getSize(currentObj.CurrentMaxCombo, osuModFlashlight); double objectOpacity = osuCurrent.OpacityAt(currentHitObject.StartTime, hidden); // Consider the jump from the lazy end position for sliders. @@ -131,15 +132,24 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd return result; } - private static double getComboScaleFor(int combo) + private static float getSize(int combo, OsuModFlashlight osuModFlashlight) + { + float size = osuModFlashlight.DefaultFlashlightSize * osuModFlashlight.SizeMultiplier.Value; + + if (osuModFlashlight.ComboBasedSize.Value) + size *= getComboScaleFor(combo); + + return size; + } + + private static float getComboScaleFor(int combo) { - // Taken from ModFlashlight. if (combo >= 200) - return 125.0; + return 0.625f; if (combo >= 100) - return 162.5; + return 0.8125f; - return 200.0; + return 1.0f; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 3d6d3f99c174..7a1d52bb40ad 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -17,11 +17,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class Flashlight : StrainSkill { private readonly bool hasHiddenMod; + private readonly OsuModFlashlight osuModFlashlight; public Flashlight(Mod[] mods) : base(mods) { hasHiddenMod = mods.Any(m => m is OsuModHidden); + osuModFlashlight = (OsuModFlashlight)mods.Single(m => m is OsuModFlashlight); } private double skillMultiplier => 0.052; @@ -36,7 +38,7 @@ 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, osuModFlashlight) * skillMultiplier; return currentStrain; } From 005a5de4e196258b68c1c847000890f660ae4b68 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Thu, 7 Nov 2024 16:16:01 +0000 Subject: [PATCH 17/21] use better logic to calculate an object's combo --- .../Preprocessing/OsuDifficultyHitObject.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 8f0b23d5f211..aba4f1e143e3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -343,15 +343,20 @@ private Vector2 getEndCursorPosition(OsuHitObject hitObject) private int getObjectCombo(HitObject hitObject) { - if (hitObject is Slider slider) + int combo = 0; + + addCombo(hitObject, ref combo); + + return combo; + + static void addCombo(HitObject hitObject, ref int combo) { - if (slider.NestedHitObjects[1] is SliderRepeat) - return slider.RepeatCount + 2; - else - return slider.NestedHitObjects.Count; - } + if (hitObject.Judgement.MaxResult.AffectsCombo()) + combo++; - return 1; + foreach (var nested in hitObject.NestedHitObjects) + addCombo(nested, ref combo); + } } } } From eb584079c882441e38c53184c6b7e1d731d8444b Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Thu, 7 Nov 2024 16:23:54 +0000 Subject: [PATCH 18/21] fix flashlight difficulty to performance formula --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index ead037ea42c6..f0738c510488 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -45,6 +45,6 @@ protected override double StrainValueAt(DifficultyHitObject current) public override double DifficultyValue() => GetCurrentStrainPeaks().Sum(); - public static double DifficultyToPerformance(double difficulty) => 25 * Math.Pow(difficulty, 2); + public static double DifficultyToPerformance(double difficulty) => 28.727 * Math.Pow(difficulty, 2); } } From de9b130185ecc617d26a35776b94bef98f4aa6ce Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Sun, 12 Jan 2025 09:29:34 +0800 Subject: [PATCH 19/21] general balancing nerfs streams and very very long maps --- .../Difficulty/Evaluators/FlashlightEvaluator.cs | 7 +++---- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs index 6ddd30c09752..2e3992c7309b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs @@ -42,13 +42,12 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd double scalingFactor = 52.0 / osuHitObject.Radius; double smallDistNerf = 1.0; double cumulativeStrainTime = 0.0; + double angleRepeatCount = 0.0; double result = 0.0; OsuDifficultyHitObject lastObj = osuCurrent; - double angleRepeatCount = 0.0; - // This is iterating backwards in time from the current object. for (int i = 0; i < Math.Min(current.Index, 10); i++) { @@ -76,10 +75,10 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd // Small jumps within the visible Flashlight radius should be nerfed. if (i == 0) - smallDistNerf = Math.Min(1.0, pixelDistance / (flashlightRadius - 45)); + smallDistNerf = Math.Min(1.0, pixelDistance / (flashlightRadius - 35)); // Nerf stacks so that only the first object of the stack is accounted for. - double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); + double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 45.0); // Bonus based on object opacity. double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - objectOpacity); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index f0738c510488..4b1cd2e7b62a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -26,8 +26,9 @@ public Flashlight(Mod[] mods) osuModFlashlight = (OsuModFlashlight)mods.Single(m => m is OsuModFlashlight); } - private double skillMultiplier => 0.05512; + private double skillMultiplier => 0.0727; private double strainDecayBase => 0.15; + protected override double DecayWeight => 0.99984; private double currentStrain; @@ -43,8 +44,6 @@ protected override double StrainValueAt(DifficultyHitObject current) return currentStrain; } - public override double DifficultyValue() => GetCurrentStrainPeaks().Sum(); - public static double DifficultyToPerformance(double difficulty) => 28.727 * Math.Pow(difficulty, 2); } } From b8740eab967fcc2dab388e3895e432a048281fff Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Sun, 12 Jan 2025 10:12:41 +0800 Subject: [PATCH 20/21] simplify flashlight max combo scaling --- 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 1919129fbbc0..1861c6c40c3b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -294,7 +294,7 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a flashlightValue *= getComboScalingFactor(attributes); // Account for shorter maps having more time played at a larger flashlight radius. - flashlightValue *= Math.Min(1, 0.7 + 0.1 * Math.Max(0, Math.Log(attributes.MaxCombo / 100.0)) + 0.1 * Math.Max(0, Math.Log(attributes.MaxCombo / 200.0))); + flashlightValue *= Math.Min(1.2 - Math.Pow(0.997, attributes.MaxCombo), 1); // Account for scores where the flashlight radius is increased due to misses. int missingCombo = attributes.MaxCombo - scoreMaxCombo; From f92696ba7803364a97ce452c33f6c05334822093 Mon Sep 17 00:00:00 2001 From: molneya <62799417+molneya@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:16:35 +0800 Subject: [PATCH 21/21] account for size multiplier setting in flashlight pp --- .../Difficulty/OsuPerformanceCalculator.cs | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index c8a7414aa9e6..103801afdecf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -298,6 +298,8 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a if (!score.Mods.Any(h => h is OsuModFlashlight)) return 0.0; + var osuModFlashlight = (OsuModFlashlight)score.Mods.Single(m => m is OsuModFlashlight); + double flashlightValue = Flashlight.DifficultyToPerformance(attributes.FlashlightDifficulty); // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. @@ -306,21 +308,32 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a flashlightValue *= getComboScalingFactor(attributes); - // Account for shorter maps having more time played at a larger flashlight radius. + // Account for shorter maps having more time played at a larger flashlight radius, and being generally more easily retryable. flashlightValue *= Math.Min(1.2 - Math.Pow(0.997, attributes.MaxCombo), 1); - // Account for scores where the flashlight radius is increased due to misses. - int missingCombo = attributes.MaxCombo - scoreMaxCombo; - double missingComboPercentage = (double)missingCombo / attributes.MaxCombo; + // Calculate time spent at each flashlight radius to account for scores where the radius increased due to misses. + double maximumSizePercentage = 1.0; + double mediumSizePercentage = 0.0; + double minimumSizePercentage = 0.0; + + if (osuModFlashlight.ComboBasedSize.Value) + { + int missingCombo = attributes.MaxCombo - scoreMaxCombo; + double missingComboPercentage = (double)missingCombo / attributes.MaxCombo; - // For balancing purposes, assume the player made 3 misses for every memorisation mistake. - double averageMissingComboLength = Math.Max(missingCombo, 1) / Math.Max(effectiveMissCount / 3, 1); + // For balancing purposes, assume the player made 3 misses for every memorisation mistake. + double averageMissingComboLength = Math.Max(missingCombo, 1) / Math.Max(effectiveMissCount / 3, 1); + + maximumSizePercentage = Math.Clamp(averageMissingComboLength, 0, 100) / averageMissingComboLength * missingComboPercentage; + mediumSizePercentage = Math.Clamp(averageMissingComboLength - 100, 0, 100) / averageMissingComboLength * missingComboPercentage; + minimumSizePercentage = 1.0 - mediumSizePercentage - maximumSizePercentage; + } - double maximumSizePercentage = Math.Clamp(averageMissingComboLength, 0, 100) / averageMissingComboLength * missingComboPercentage; - double mediumSizePercentage = Math.Clamp(averageMissingComboLength - 100, 0, 100) / averageMissingComboLength * missingComboPercentage; - double minimumSizePercentage = 1.0 - mediumSizePercentage - maximumSizePercentage; + double maximumSizeScalingFactor = flashlightRadiusScalingFactor(osuModFlashlight.SizeMultiplier.Value); + double mediumSizeScalingFactor = flashlightRadiusScalingFactor(0.8125f * osuModFlashlight.SizeMultiplier.Value); + double minimumSizeScalingFactor = flashlightRadiusScalingFactor(0.625f * osuModFlashlight.SizeMultiplier.Value); - flashlightValue *= minimumSizePercentage + 0.6 * mediumSizePercentage + 0.2 * maximumSizePercentage; + flashlightValue *= maximumSizeScalingFactor * maximumSizePercentage + mediumSizeScalingFactor * mediumSizePercentage + minimumSizeScalingFactor * minimumSizePercentage; // Scale the flashlight value with accuracy _slightly_. flashlightValue *= 0.5 + accuracy / 2.0; @@ -437,6 +450,7 @@ private double calculateSpeedHighDeviationNerf(OsuDifficultyAttributes attribute // to make it more punishing on maps with lower amount of hard sections. private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.96 / ((missCount / (4 * Math.Pow(Math.Log(difficultStrainCount), 0.94))) + 1); private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); + private double flashlightRadiusScalingFactor(double sizeMultiplier) => 1.2 / (1 + Math.Exp(8.58367 * (sizeMultiplier - 0.8125))); private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh;