1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-18 11:02:57 +08:00

Change slider drop penalty to use actual number of difficult sliders, fix slider drop penalty being too lenient (#31055)

* Change slider drop penalty to use actual number of difficult sliders, fix slider nerf being too lenient

* Move cubing to performance calculation

* Add separate list for slider strains

* Rename difficulty atttribute

* Rename attribute in perfcalc

* Check if AimDifficultSliderCount is more than 0, code quality fixes

* Add `AimDifficultSliderCount` to the list of databased attributes

* Code quality

---------

Co-authored-by: James Wilson <tsunyoku@gmail.com>
This commit is contained in:
StanR 2024-12-22 04:45:29 +05:00 committed by GitHub
parent f6a36f7b2e
commit 6808a5a77c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 60 additions and 26 deletions

View File

@ -8,6 +8,7 @@ using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty
{
@ -19,6 +20,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
[JsonProperty("aim_difficulty")]
public double AimDifficulty { get; set; }
/// <summary>
/// The number of <see cref="Slider"/>s weighted by difficulty.
/// </summary>
[JsonProperty("aim_difficult_slider_count")]
public double AimDifficultSliderCount { get; set; }
/// <summary>
/// The difficulty corresponding to the speed skill.
/// </summary>
@ -109,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
yield return (ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT, AimDifficultStrainCount);
yield return (ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT, SpeedDifficultStrainCount);
yield return (ATTRIB_ID_SPEED_NOTE_COUNT, SpeedNoteCount);
yield return (ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT, AimDifficultSliderCount);
}
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
@ -125,6 +133,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
AimDifficultStrainCount = values[ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT];
SpeedDifficultStrainCount = values[ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT];
SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT];
AimDifficultSliderCount = values[ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT];
DrainRate = onlineInfo.DrainRate;
HitCircleCount = onlineInfo.CircleCount;
SliderCount = onlineInfo.SliderCount;

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
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 difficultSliders = ((Aim)skills[0]).GetDifficultSliders();
double flashlightRating = 0.0;
if (mods.Any(h => h is OsuModFlashlight))
@ -99,6 +99,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
StarRating = starRating,
Mods = mods,
AimDifficulty = aimRating,
AimDifficultSliderCount = difficultSliders,
SpeedDifficulty = speedRating,
SpeedNoteCount = speedNotes,
FlashlightDifficulty = flashlightRating,

View File

@ -135,7 +135,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
{
double aimValue = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty);
double aimDifficulty = attributes.AimDifficulty;
if (attributes.SliderCount > 0 && attributes.AimDifficultSliderCount > 0)
{
double estimateImproperlyFollowedDifficultSliders;
if (usingClassicSliderAccuracy)
{
// When the score is considered classic (regardless if it was made on old client or not) we consider all missing combo to be dropped difficult sliders
int maximumPossibleDroppedSliders = totalImperfectHits;
estimateImproperlyFollowedDifficultSliders = Math.Clamp(Math.Min(maximumPossibleDroppedSliders, attributes.MaxCombo - scoreMaxCombo), 0, attributes.AimDifficultSliderCount);
}
else
{
// We add tick misses here since they too mean that the player didn't follow the slider properly
// We however aren't adding misses here because missing slider heads has a harsh penalty by itself and doesn't mean that the rest of the slider wasn't followed properly
estimateImproperlyFollowedDifficultSliders = Math.Min(countSliderEndsDropped + countSliderTickMiss, attributes.AimDifficultSliderCount);
}
double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateImproperlyFollowedDifficultSliders / attributes.AimDifficultSliderCount, 3) + attributes.SliderFactor;
aimDifficulty *= sliderNerfFactor;
}
double aimValue = OsuStrainSkill.DifficultyToPerformance(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);
@ -163,30 +186,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
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;
if (attributes.SliderCount > 0)
{
double estimateImproperlyFollowedDifficultSliders;
if (usingClassicSliderAccuracy)
{
// When the score is considered classic (regardless if it was made on old client or not) we consider all missing combo to be dropped difficult sliders
int maximumPossibleDroppedSliders = totalImperfectHits;
estimateImproperlyFollowedDifficultSliders = Math.Clamp(Math.Min(maximumPossibleDroppedSliders, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
}
else
{
// We add tick misses here since they too mean that the player didn't follow the slider properly
// We however aren't adding misses here because missing slider heads has a harsh penalty by itself and doesn't mean that the rest of the slider wasn't followed properly
estimateImproperlyFollowedDifficultSliders = Math.Clamp(countSliderEndsDropped + countSliderTickMiss, 0, estimateDifficultSliders);
}
double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateImproperlyFollowedDifficultSliders / estimateDifficultSliders, 3) + attributes.SliderFactor;
aimValue *= sliderNerfFactor;
}
aimValue *= accuracy;
// It is important to consider accuracy difficulty when scaling with accuracy.
aimValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500;

View File

@ -2,9 +2,12 @@
// 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;
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
@ -26,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private double skillMultiplier => 25.18;
private double strainDecayBase => 0.15;
private readonly List<double> sliderStrains = new List<double>();
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);
@ -35,7 +40,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
currentStrain *= strainDecay(current.DeltaTime);
currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier;
if (current.BaseObject is Slider)
{
sliderStrains.Add(currentStrain);
}
return currentStrain;
}
public double GetDifficultSliders()
{
if (sliderStrains.Count == 0)
return 0;
double[] sortedStrains = sliderStrains.OrderDescending().ToArray();
double maxSliderStrain = sortedStrains.Max();
if (maxSliderStrain == 0)
return 0;
return sortedStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxSliderStrain * 12.0 - 6.0))));
}
}
}

View File

@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Difficulty
protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 25;
protected const int ATTRIB_ID_OK_HIT_WINDOW = 27;
protected const int ATTRIB_ID_MONO_STAMINA_FACTOR = 29;
protected const int ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT = 31;
/// <summary>
/// The mods which were applied to the beatmap.