mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 09:32:55 +08:00
Merge pull request #16135 from stanriders/refactor-diffcalc
Slightly refactor difficulty and pp calculators
This commit is contained in:
commit
67e6b50c12
@ -0,0 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public class CatchPerformanceAttributes : PerformanceAttributes
|
||||
{
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
}
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
public override PerformanceAttributes Calculate()
|
||||
{
|
||||
mods = Score.Mods;
|
||||
|
||||
@ -44,15 +44,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
// Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo
|
||||
int numTotalHits = totalComboHits();
|
||||
|
||||
// Longer maps are worth more
|
||||
double lengthBonus =
|
||||
0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) +
|
||||
(numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
|
||||
|
||||
// Longer maps are worth more
|
||||
value *= lengthBonus;
|
||||
|
||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||
value *= Math.Pow(0.97, misses);
|
||||
|
||||
// Combo scaling
|
||||
@ -80,17 +76,17 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
}
|
||||
|
||||
if (mods.Any(m => m is ModFlashlight))
|
||||
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
|
||||
value *= 1.35 * lengthBonus;
|
||||
|
||||
// Scale the aim value with accuracy _slightly_
|
||||
value *= Math.Pow(accuracy(), 5.5);
|
||||
|
||||
// Custom multipliers for NoFail. SpunOut is not applicable.
|
||||
if (mods.Any(m => m is ModNoFail))
|
||||
value *= 0.90;
|
||||
|
||||
return value;
|
||||
return new CatchPerformanceAttributes
|
||||
{
|
||||
Total = value
|
||||
};
|
||||
}
|
||||
|
||||
private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1);
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
public class ManiaPerformanceAttributes : PerformanceAttributes
|
||||
{
|
||||
[JsonProperty("difficulty")]
|
||||
public double Difficulty { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
[JsonProperty("scaled_score")]
|
||||
public double ScaledScore { get; set; }
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
}
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
public override PerformanceAttributes Calculate()
|
||||
{
|
||||
mods = Score.Mods;
|
||||
scaledScore = Score.TotalScore;
|
||||
@ -61,48 +61,46 @@ 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)
|
||||
return new ManiaPerformanceAttributes
|
||||
{
|
||||
categoryDifficulty["Strain"] = strainValue;
|
||||
categoryDifficulty["Accuracy"] = accValue;
|
||||
}
|
||||
|
||||
return totalValue;
|
||||
Difficulty = difficultyValue,
|
||||
Accuracy = accValue,
|
||||
ScaledScore = scaledScore,
|
||||
Total = 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;
|
||||
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,12 +108,9 @@ 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
|
||||
// accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
|
||||
|
||||
return accuracyValue;
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
26
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs
Normal file
26
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuPerformanceAttributes : PerformanceAttributes
|
||||
{
|
||||
[JsonProperty("aim")]
|
||||
public double Aim { get; set; }
|
||||
|
||||
[JsonProperty("speed")]
|
||||
public double Speed { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
[JsonProperty("flashlight")]
|
||||
public double Flashlight { get; set; }
|
||||
|
||||
[JsonProperty("effective_miss_count")]
|
||||
public double EffectiveMissCount { get; set; }
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
}
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryRatings = null)
|
||||
public override PerformanceAttributes Calculate()
|
||||
{
|
||||
mods = Score.Mods;
|
||||
accuracy = Score.Accuracy;
|
||||
@ -45,7 +45,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||
|
||||
// Custom multipliers for NoFail and SpunOut.
|
||||
if (mods.Any(m => m is OsuModNoFail))
|
||||
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
|
||||
|
||||
@ -72,42 +71,35 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
Math.Pow(flashlightValue, 1.1), 1.0 / 1.1
|
||||
) * multiplier;
|
||||
|
||||
if (categoryRatings != null)
|
||||
return new OsuPerformanceAttributes
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
return totalValue;
|
||||
Aim = aimValue,
|
||||
Speed = speedValue,
|
||||
Accuracy = accuracyValue,
|
||||
Flashlight = flashlightValue,
|
||||
EffectiveMissCount = effectiveMissCount,
|
||||
Total = totalValue
|
||||
};
|
||||
}
|
||||
|
||||
private double computeAimValue()
|
||||
{
|
||||
double rawAim = Attributes.AimStrain;
|
||||
double rawAim = Attributes.AimDifficulty;
|
||||
|
||||
if (mods.Any(m => m is OsuModTouchDevice))
|
||||
rawAim = Math.Pow(rawAim, 0.8);
|
||||
|
||||
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 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) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
|
||||
aimValue *= lengthBonus;
|
||||
|
||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||
if (effectiveMissCount > 0)
|
||||
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
|
||||
|
||||
// Combo scaling.
|
||||
if (Attributes.MaxCombo > 0)
|
||||
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
aimValue *= getComboScalingFactor();
|
||||
|
||||
double approachRateFactor = 0.0;
|
||||
if (Attributes.ApproachRate > 10.33)
|
||||
@ -136,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
}
|
||||
|
||||
aimValue *= accuracy;
|
||||
// It is important to also consider accuracy difficulty when doing that.
|
||||
// It is important to consider accuracy difficulty when scaling with accuracy.
|
||||
aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
|
||||
|
||||
return aimValue;
|
||||
@ -144,9 +136,8 @@ 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) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
speedValue *= lengthBonus;
|
||||
@ -155,9 +146,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (effectiveMissCount > 0)
|
||||
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
|
||||
|
||||
// Combo scaling.
|
||||
if (Attributes.MaxCombo > 0)
|
||||
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
speedValue *= getComboScalingFactor();
|
||||
|
||||
double approachRateFactor = 0.0;
|
||||
if (Attributes.ApproachRate > 10.33)
|
||||
@ -227,14 +216,13 @@ 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);
|
||||
|
||||
double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0;
|
||||
|
||||
// Add an additional bonus for HDFL.
|
||||
if (mods.Any(h => h is OsuModHidden))
|
||||
flashlightValue *= 1.3;
|
||||
|
||||
@ -242,9 +230,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (effectiveMissCount > 0)
|
||||
flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
|
||||
|
||||
// Combo scaling.
|
||||
if (Attributes.MaxCombo > 0)
|
||||
flashlightValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
flashlightValue *= getComboScalingFactor();
|
||||
|
||||
// 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) +
|
||||
@ -276,6 +262,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
|
||||
}
|
||||
|
||||
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -0,0 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
public class TaikoPerformanceAttributes : PerformanceAttributes
|
||||
{
|
||||
[JsonProperty("difficulty")]
|
||||
public double Difficulty { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
public double Accuracy { get; set; }
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
}
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
public override PerformanceAttributes Calculate()
|
||||
{
|
||||
mods = Score.Mods;
|
||||
countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||
@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
|
||||
// Custom multipliers for NoFail and SpunOut.
|
||||
double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||
|
||||
if (mods.Any(m => m is ModNoFail))
|
||||
@ -44,43 +43,38 @@ 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)
|
||||
return new TaikoPerformanceAttributes
|
||||
{
|
||||
categoryDifficulty["Strain"] = strainValue;
|
||||
categoryDifficulty["Accuracy"] = accuracyValue;
|
||||
}
|
||||
|
||||
return totalValue;
|
||||
Difficulty = difficultyValue,
|
||||
Accuracy = accuracyValue,
|
||||
Total = 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()
|
||||
@ -88,11 +82,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
if (Attributes.GreatHitWindow <= 0)
|
||||
return 0;
|
||||
|
||||
// Lots of arbitrary values from testing.
|
||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
||||
double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
|
||||
|
||||
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
|
||||
// Bonus for many objects - it's harder to keep good accuracy up for longer
|
||||
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
16
osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs
Normal file
16
osu.Game/Rulesets/Difficulty/PerformanceAttributes.cs
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty
|
||||
{
|
||||
public class PerformanceAttributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculated score performance points.
|
||||
/// </summary>
|
||||
[JsonProperty("pp")]
|
||||
public double Total { get; set; }
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
@ -37,6 +36,6 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
TimeRate = track.Rate;
|
||||
}
|
||||
|
||||
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
|
||||
public abstract PerformanceAttributes Calculate();
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Scoring
|
||||
|
||||
var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Value.Attributes, score);
|
||||
|
||||
return calculator?.Calculate();
|
||||
return calculator?.Calculate().Total;
|
||||
}
|
||||
|
||||
public readonly struct PerformanceCacheLookup
|
||||
|
@ -129,7 +129,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, scoreInfo);
|
||||
|
||||
Current.Value = (int)Math.Round(calculator?.Calculate() ?? 0, MidpointRounding.AwayFromZero);
|
||||
Current.Value = (int)Math.Round(calculator?.Calculate().Total ?? 0, MidpointRounding.AwayFromZero);
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user