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:
parent
f6a36f7b2e
commit
6808a5a77c
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user