1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-15 17:47:18 +08:00

Slightly refactor difficulty and pp calculators

This commit is contained in:
StanR 2021-12-17 23:39:03 +03:00
parent ed5d7046f3
commit 2f2006715e
10 changed files with 85 additions and 72 deletions

View File

@ -90,6 +90,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty
if (mods.Any(m => m is ModNoFail))
value *= 0.90;
if (categoryDifficulty != null)
{
categoryDifficulty.Add("AR", Attributes.ApproachRate);
categoryDifficulty.Add("Max Combo", Attributes.MaxCombo);
}
return value;
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
yield return v;
// Todo: osu!mania doesn't output MaxCombo attribute for some reason.
yield return (ATTRIB_ID_STRAIN, StarRating);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
}
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
base.FromDatabaseAttributes(values);
StarRating = values[ATTRIB_ID_STRAIN];
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
}

View File

@ -61,48 +61,51 @@ namespace osu.Game.Rulesets.Mania.Difficulty
if (mods.Any(m => m is ModEasy))
multiplier *= 0.5;
double strainValue = computeStrainValue();
double accValue = computeAccuracyValue(strainValue);
double difficultyValue = computeDifficultyValue();
double accValue = computeAccuracyValue(difficultyValue);
double totalValue =
Math.Pow(
Math.Pow(strainValue, 1.1) +
Math.Pow(difficultyValue, 1.1) +
Math.Pow(accValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryDifficulty != null)
{
categoryDifficulty["Strain"] = strainValue;
categoryDifficulty["Accuracy"] = accValue;
categoryDifficulty.Add("Difficulty", difficultyValue);
categoryDifficulty.Add("Accuracy", accValue);
categoryDifficulty.Add("Scaled Score", scaledScore);
categoryDifficulty.Add("Great Hit Window", Attributes.GreatHitWindow);
categoryDifficulty.Add("Max Combo", Attributes.MaxCombo);
}
return totalValue;
}
private double computeStrainValue()
private double computeDifficultyValue()
{
// Obtain strain difficulty
double strainValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
// Obtain difficulty
double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
// Longer maps are worth more
strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
if (scaledScore <= 500000)
strainValue = 0;
difficultyValue = 0;
else if (scaledScore <= 600000)
strainValue *= (scaledScore - 500000) / 100000 * 0.3;
difficultyValue *= (scaledScore - 500000) / 100000 * 0.3;
else if (scaledScore <= 700000)
strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
difficultyValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
else if (scaledScore <= 800000)
strainValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
difficultyValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
else if (scaledScore <= 900000)
strainValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
difficultyValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
else
strainValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
difficultyValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
return strainValue;
return difficultyValue;
}
private double computeAccuracyValue(double strainValue)
private double computeAccuracyValue(double difficultyValue)
{
if (Attributes.GreatHitWindow <= 0)
return 0;
@ -110,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
* strainValue
* difficultyValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer

View File

@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuDifficultyAttributes : DifficultyAttributes
{
[JsonProperty("aim_strain")]
public double AimStrain { get; set; }
[JsonProperty("aim_difficulty")]
public double AimDifficulty { get; set; }
[JsonProperty("speed_strain")]
public double SpeedStrain { get; set; }
[JsonProperty("speed_difficulty")]
public double SpeedDifficulty { get; set; }
[JsonProperty("flashlight_rating")]
public double FlashlightRating { get; set; }
[JsonProperty("flashlight_difficulty")]
public double FlashlightDifficulty { get; set; }
[JsonProperty("slider_factor")]
public double SliderFactor { get; set; }
@ -43,15 +43,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
foreach (var v in base.ToDatabaseAttributes())
yield return v;
yield return (ATTRIB_ID_AIM, AimStrain);
yield return (ATTRIB_ID_SPEED, SpeedStrain);
yield return (ATTRIB_ID_AIM, AimDifficulty);
yield return (ATTRIB_ID_SPEED, SpeedDifficulty);
yield return (ATTRIB_ID_OVERALL_DIFFICULTY, OverallDifficulty);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (ATTRIB_ID_STRAIN, StarRating);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
if (ShouldSerializeFlashlightRating())
yield return (ATTRIB_ID_FLASHLIGHT, FlashlightRating);
yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty);
yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor);
}
@ -60,13 +60,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
base.FromDatabaseAttributes(values);
AimStrain = values[ATTRIB_ID_AIM];
SpeedStrain = values[ATTRIB_ID_SPEED];
AimDifficulty = values[ATTRIB_ID_AIM];
SpeedDifficulty = values[ATTRIB_ID_SPEED];
OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_STRAIN];
FlashlightRating = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
StarRating = values[ATTRIB_ID_DIFFICULTY];
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
}

View File

@ -74,9 +74,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
StarRating = starRating,
Mods = mods,
AimStrain = aimRating,
SpeedStrain = speedRating,
FlashlightRating = flashlightRating,
AimDifficulty = aimRating,
SpeedDifficulty = speedRating,
FlashlightDifficulty = flashlightRating,
SliderFactor = sliderFactor,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
}
public override double Calculate(Dictionary<string, double> categoryRatings = null)
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
mods = Score.Mods;
accuracy = Score.Accuracy;
@ -72,15 +72,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty
Math.Pow(flashlightValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryRatings != null)
if (categoryDifficulty != null)
{
categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue);
categoryRatings.Add("Flashlight", flashlightValue);
categoryRatings.Add("OD", Attributes.OverallDifficulty);
categoryRatings.Add("AR", Attributes.ApproachRate);
categoryRatings.Add("Max Combo", Attributes.MaxCombo);
categoryDifficulty.Add("Aim", aimValue);
categoryDifficulty.Add("Speed", speedValue);
categoryDifficulty.Add("Accuracy", accuracyValue);
categoryDifficulty.Add("Flashlight", flashlightValue);
categoryDifficulty.Add("OD", Attributes.OverallDifficulty);
categoryDifficulty.Add("AR", Attributes.ApproachRate);
categoryDifficulty.Add("Max Combo", Attributes.MaxCombo);
categoryDifficulty.Add("Effective Miss Count", effectiveMissCount);
}
return totalValue;
@ -88,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeAimValue()
{
double rawAim = Attributes.AimStrain;
double rawAim = Attributes.AimDifficulty;
if (mods.Any(m => m is OsuModTouchDevice))
rawAim = Math.Pow(rawAim, 0.8);
@ -144,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeSpeedValue()
{
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
@ -227,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (!mods.Any(h => h is OsuModFlashlight))
return 0.0;
double rawFlashlight = Attributes.FlashlightRating;
double rawFlashlight = Attributes.FlashlightDifficulty;
if (mods.Any(m => m is OsuModTouchDevice))
rawFlashlight = Math.Pow(rawFlashlight, 0.8);

View File

@ -9,14 +9,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoDifficultyAttributes : DifficultyAttributes
{
[JsonProperty("stamina_strain")]
public double StaminaStrain { get; set; }
[JsonProperty("stamina_difficulty")]
public double StaminaDifficulty { get; set; }
[JsonProperty("rhythm_strain")]
public double RhythmStrain { get; set; }
[JsonProperty("rhythm_difficulty")]
public double RhythmDifficulty { get; set; }
[JsonProperty("colour_strain")]
public double ColourStrain { get; set; }
[JsonProperty("colour_difficulty")]
public double ColourDifficulty { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
yield return v;
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (ATTRIB_ID_STRAIN, StarRating);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
}
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
base.FromDatabaseAttributes(values);
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_STRAIN];
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
}
}

View File

@ -91,9 +91,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
StarRating = starRating,
Mods = mods,
StaminaStrain = staminaRating,
RhythmStrain = rhythmRating,
ColourStrain = colourRating,
StaminaDifficulty = staminaRating,
RhythmDifficulty = rhythmRating,
ColourDifficulty = colourRating,
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
};

View File

@ -44,43 +44,46 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (mods.Any(m => m is ModHidden))
multiplier *= 1.10;
double strainValue = computeStrainValue();
double difficultyValue = computeDifficultyValue();
double accuracyValue = computeAccuracyValue();
double totalValue =
Math.Pow(
Math.Pow(strainValue, 1.1) +
Math.Pow(difficultyValue, 1.1) +
Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryDifficulty != null)
{
categoryDifficulty["Strain"] = strainValue;
categoryDifficulty["Accuracy"] = accuracyValue;
categoryDifficulty.Add("Difficulty", difficultyValue);
categoryDifficulty.Add("Accuracy", accuracyValue);
categoryDifficulty.Add("AR", Attributes.ApproachRate);
categoryDifficulty.Add("Great Hit Window", Attributes.GreatHitWindow);
categoryDifficulty.Add("Max Combo", Attributes.MaxCombo);
}
return totalValue;
}
private double computeStrainValue()
private double computeDifficultyValue()
{
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
// Longer maps are worth more
double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
strainValue *= lengthBonus;
difficultyValue *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
strainValue *= Math.Pow(0.985, countMiss);
difficultyValue *= Math.Pow(0.985, countMiss);
if (mods.Any(m => m is ModHidden))
strainValue *= 1.025;
difficultyValue *= 1.025;
if (mods.Any(m => m is ModFlashlight<TaikoHitObject>))
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
strainValue *= 1.05 * lengthBonus;
difficultyValue *= 1.05 * lengthBonus;
// Scale the speed value with accuracy _slightly_
return strainValue * Score.Accuracy;
return difficultyValue * Score.Accuracy;
}
private double computeAccuracyValue()

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Difficulty
protected const int ATTRIB_ID_OVERALL_DIFFICULTY = 5;
protected const int ATTRIB_ID_APPROACH_RATE = 7;
protected const int ATTRIB_ID_MAX_COMBO = 9;
protected const int ATTRIB_ID_STRAIN = 11;
protected const int ATTRIB_ID_DIFFICULTY = 11;
protected const int ATTRIB_ID_GREAT_HIT_WINDOW = 13;
protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15;
protected const int ATTRIB_ID_FLASHLIGHT = 17;