1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-20 09:03:00 +08:00

Restructure PerformanceCalculator to not require ScoreInfo argument

This commit is contained in:
Dan Balasescu 2022-03-14 14:25:26 +09:00
parent fa456da0ec
commit 4a3e3aba65
14 changed files with 147 additions and 189 deletions

View File

@ -19,7 +19,6 @@ using osu.Game.Rulesets.Catch.Difficulty;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using System;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Catch.Edit;
@ -182,7 +181,7 @@ namespace osu.Game.Rulesets.Catch
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin);
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score);
public override PerformanceCalculator CreatePerformanceCalculator() => new CatchPerformanceCalculator(this);
public int LegacyID => 2;

View File

@ -13,33 +13,29 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchPerformanceCalculator : PerformanceCalculator
{
protected new CatchDifficultyAttributes Attributes => (CatchDifficultyAttributes)base.Attributes;
private Mod[] mods;
private int fruitsHit;
private int ticksHit;
private int tinyTicksHit;
private int tinyTicksMissed;
private int misses;
public CatchPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
: base(ruleset, attributes, score)
public CatchPerformanceCalculator(Ruleset ruleset)
: base(ruleset)
{
}
public override PerformanceAttributes Calculate()
protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
{
mods = Score.Mods;
var catchAttributes = (CatchDifficultyAttributes)attributes;
fruitsHit = Score.Statistics.GetValueOrDefault(HitResult.Great);
ticksHit = Score.Statistics.GetValueOrDefault(HitResult.LargeTickHit);
tinyTicksHit = Score.Statistics.GetValueOrDefault(HitResult.SmallTickHit);
tinyTicksMissed = Score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss);
misses = Score.Statistics.GetValueOrDefault(HitResult.Miss);
fruitsHit = score.Statistics.GetValueOrDefault(HitResult.Great);
ticksHit = score.Statistics.GetValueOrDefault(HitResult.LargeTickHit);
tinyTicksHit = score.Statistics.GetValueOrDefault(HitResult.SmallTickHit);
tinyTicksMissed = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss);
misses = score.Statistics.GetValueOrDefault(HitResult.Miss);
// We are heavily relying on aim in catch the beat
double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
double value = Math.Pow(5.0 * Math.Max(1.0, catchAttributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
// Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo
int numTotalHits = totalComboHits();
@ -52,10 +48,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
value *= Math.Pow(0.97, misses);
// Combo scaling
if (Attributes.MaxCombo > 0)
value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
if (catchAttributes.MaxCombo > 0)
value *= Math.Min(Math.Pow(score.MaxCombo, 0.8) / Math.Pow(catchAttributes.MaxCombo, 0.8), 1.0);
double approachRate = Attributes.ApproachRate;
double approachRate = catchAttributes.ApproachRate;
double approachRateFactor = 1.0;
if (approachRate > 9.0)
approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9
@ -66,7 +62,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
value *= approachRateFactor;
if (mods.Any(m => m is ModHidden))
if (score.Mods.Any(m => m is ModHidden))
{
// Hiddens gives almost nothing on max approach rate, and more the lower it is
if (approachRate <= 10.0)
@ -75,12 +71,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty
value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11
}
if (mods.Any(m => m is ModFlashlight))
if (score.Mods.Any(m => m is ModFlashlight))
value *= 1.35 * lengthBonus;
value *= Math.Pow(accuracy(), 5.5);
if (mods.Any(m => m is ModNoFail))
if (score.Mods.Any(m => m is ModNoFail))
value *= 0.90;
return new CatchPerformanceAttributes

View File

@ -13,10 +13,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaPerformanceCalculator : PerformanceCalculator
{
protected new ManiaDifficultyAttributes Attributes => (ManiaDifficultyAttributes)base.Attributes;
private Mod[] mods;
// Score after being scaled by non-difficulty-increasing mods
private double scaledScore;
@ -27,39 +23,40 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private int countMeh;
private int countMiss;
public ManiaPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
: base(ruleset, attributes, score)
public ManiaPerformanceCalculator(Ruleset ruleset)
: base(ruleset)
{
}
public override PerformanceAttributes Calculate()
protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
{
mods = Score.Mods;
scaledScore = Score.TotalScore;
countPerfect = Score.Statistics.GetValueOrDefault(HitResult.Perfect);
countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great);
countGood = Score.Statistics.GetValueOrDefault(HitResult.Good);
countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
var maniaAttributes = (ManiaDifficultyAttributes)attributes;
if (Attributes.ScoreMultiplier > 0)
scaledScore = score.TotalScore;
countPerfect = score.Statistics.GetValueOrDefault(HitResult.Perfect);
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
countGood = score.Statistics.GetValueOrDefault(HitResult.Good);
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
if (maniaAttributes.ScoreMultiplier > 0)
{
// Scale score up, so it's comparable to other keymods
scaledScore *= 1.0 / Attributes.ScoreMultiplier;
scaledScore *= 1.0 / maniaAttributes.ScoreMultiplier;
}
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
// The specific number has no intrinsic meaning and can be adjusted as needed.
double multiplier = 0.8;
if (mods.Any(m => m is ModNoFail))
if (score.Mods.Any(m => m is ModNoFail))
multiplier *= 0.9;
if (mods.Any(m => m is ModEasy))
if (score.Mods.Any(m => m is ModEasy))
multiplier *= 0.5;
double difficultyValue = computeDifficultyValue();
double accValue = computeAccuracyValue(difficultyValue);
double difficultyValue = computeDifficultyValue(maniaAttributes);
double accValue = computeAccuracyValue(difficultyValue, maniaAttributes);
double totalValue =
Math.Pow(
Math.Pow(difficultyValue, 1.1) +
@ -75,9 +72,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
};
}
private double computeDifficultyValue()
private double computeDifficultyValue(ManiaDifficultyAttributes attributes)
{
double difficultyValue = 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;
difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
@ -97,14 +94,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty
return difficultyValue;
}
private double computeAccuracyValue(double difficultyValue)
private double computeAccuracyValue(double difficultyValue, ManiaDifficultyAttributes attributes)
{
if (Attributes.GreatHitWindow <= 0)
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 accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
double accuracyValue = Math.Max(0.0, 0.2 - (attributes.GreatHitWindow - 34) * 0.006667)
* difficultyValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);

View File

@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new ManiaPerformanceCalculator(this, attributes, score);
public override PerformanceCalculator CreatePerformanceCalculator() => new ManiaPerformanceCalculator(this);
public const string SHORT_NAME = "mania";

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
@ -14,10 +13,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuPerformanceCalculator : PerformanceCalculator
{
public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes;
private Mod[] mods;
private double accuracy;
private int scoreMaxCombo;
private int countGreat;
@ -27,31 +22,32 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double effectiveMissCount;
public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
: base(ruleset, attributes, score)
public OsuPerformanceCalculator(Ruleset ruleset)
: base(ruleset)
{
}
public override PerformanceAttributes Calculate()
protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
{
mods = Score.Mods;
accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo;
countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great);
countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
effectiveMissCount = calculateEffectiveMissCount();
var osuAttributes = (OsuDifficultyAttributes)attributes;
accuracy = score.Accuracy;
scoreMaxCombo = score.MaxCombo;
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
effectiveMissCount = calculateEffectiveMissCount(osuAttributes);
double multiplier = 1.12; // 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 OsuModNoFail))
if (score.Mods.Any(m => m is OsuModNoFail))
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
if (mods.Any(m => m is OsuModSpunOut) && totalHits > 0)
multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
if (score.Mods.Any(m => m is OsuModSpunOut) && totalHits > 0)
multiplier *= 1.0 - Math.Pow((double)osuAttributes.SpinnerCount / totalHits, 0.85);
if (mods.Any(h => h is OsuModRelax))
if (score.Mods.Any(h => h is OsuModRelax))
{
// As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
effectiveMissCount = Math.Min(effectiveMissCount + countOk + countMeh, totalHits);
@ -59,10 +55,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
multiplier *= 0.6;
}
double aimValue = computeAimValue();
double speedValue = computeSpeedValue();
double accuracyValue = computeAccuracyValue();
double flashlightValue = computeFlashlightValue();
double aimValue = computeAimValue(score, osuAttributes);
double speedValue = computeSpeedValue(score, osuAttributes);
double accuracyValue = computeAccuracyValue(score, osuAttributes);
double flashlightValue = computeFlashlightValue(score, osuAttributes);
double totalValue =
Math.Pow(
Math.Pow(aimValue, 1.1) +
@ -82,11 +78,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
};
}
private double computeAimValue()
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
{
double rawAim = Attributes.AimDifficulty;
double rawAim = attributes.AimDifficulty;
if (mods.Any(m => m is OsuModTouchDevice))
if (score.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;
@ -99,44 +95,44 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (effectiveMissCount > 0)
aimValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount);
aimValue *= getComboScalingFactor();
aimValue *= getComboScalingFactor(attributes);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33);
else if (Attributes.ApproachRate < 8.0)
approachRateFactor = 0.1 * (8.0 - Attributes.ApproachRate);
if (attributes.ApproachRate > 10.33)
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
else if (attributes.ApproachRate < 8.0)
approachRateFactor = 0.1 * (8.0 - attributes.ApproachRate);
aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR.
if (mods.Any(m => m is OsuModBlinds))
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * Attributes.DrainRate * Attributes.DrainRate);
else if (mods.Any(h => h is OsuModHidden))
if (score.Mods.Any(m => m is OsuModBlinds))
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate);
else if (score.Mods.Any(h => h is OsuModHidden))
{
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
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;
double estimateDifficultSliders = attributes.SliderCount * 0.15;
if (Attributes.SliderCount > 0)
if (attributes.SliderCount > 0)
{
double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor;
double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / 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;
aimValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500;
return aimValue;
}
private double computeSpeedValue()
private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes)
{
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedDifficulty / 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;
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
@ -146,27 +142,27 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (effectiveMissCount > 0)
speedValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
speedValue *= getComboScalingFactor();
speedValue *= getComboScalingFactor(attributes);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33);
if (attributes.ApproachRate > 10.33)
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
speedValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR.
if (mods.Any(m => m is OsuModBlinds))
if (score.Mods.Any(m => m is OsuModBlinds))
{
// Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given.
speedValue *= 1.12;
}
else if (mods.Any(m => m is OsuModHidden))
else if (score.Mods.Any(m => m is OsuModHidden))
{
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
}
// Scale the speed value with accuracy and OD.
speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2);
speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
// Scale the speed value with # of 50s to punish doubletapping.
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
@ -174,14 +170,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return speedValue;
}
private double computeAccuracyValue()
private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes attributes)
{
if (mods.Any(h => h is OsuModRelax))
if (score.Mods.Any(h => h is OsuModRelax))
return 0.0;
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
double betterAccuracyPercentage;
int amountHitObjectsWithAccuracy = Attributes.HitCircleCount;
int amountHitObjectsWithAccuracy = attributes.HitCircleCount;
if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
@ -194,43 +190,43 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution.
double accuracyValue = Math.Pow(1.52163, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83;
double accuracyValue = Math.Pow(1.52163, attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer.
accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3));
// Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given.
if (mods.Any(m => m is OsuModBlinds))
if (score.Mods.Any(m => m is OsuModBlinds))
accuracyValue *= 1.14;
else if (mods.Any(m => m is OsuModHidden))
else if (score.Mods.Any(m => m is OsuModHidden))
accuracyValue *= 1.08;
if (mods.Any(m => m is OsuModFlashlight))
if (score.Mods.Any(m => m is OsuModFlashlight))
accuracyValue *= 1.02;
return accuracyValue;
}
private double computeFlashlightValue()
private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes)
{
if (!mods.Any(h => h is OsuModFlashlight))
if (!score.Mods.Any(h => h is OsuModFlashlight))
return 0.0;
double rawFlashlight = Attributes.FlashlightDifficulty;
double rawFlashlight = attributes.FlashlightDifficulty;
if (mods.Any(m => m is OsuModTouchDevice))
if (score.Mods.Any(m => m is OsuModTouchDevice))
rawFlashlight = Math.Pow(rawFlashlight, 0.8);
double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0;
if (mods.Any(h => h is OsuModHidden))
if (score.Mods.Any(h => h is OsuModHidden))
flashlightValue *= 1.3;
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
flashlightValue *= getComboScalingFactor();
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) +
@ -239,19 +235,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Scale the flashlight value with accuracy _slightly_.
flashlightValue *= 0.5 + accuracy / 2.0;
// It is important to also consider accuracy difficulty when doing that.
flashlightValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
flashlightValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500;
return flashlightValue;
}
private double calculateEffectiveMissCount()
private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes)
{
// Guess the number of misses + slider breaks from combo
double comboBasedMissCount = 0.0;
if (Attributes.SliderCount > 0)
if (attributes.SliderCount > 0)
{
double fullComboThreshold = Attributes.MaxCombo - 0.1 * Attributes.SliderCount;
double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount;
if (scoreMaxCombo < fullComboThreshold)
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
}
@ -262,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, 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 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 int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}

View File

@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new OsuPerformanceCalculator(this, attributes, score);
public override PerformanceCalculator CreatePerformanceCalculator() => new OsuPerformanceCalculator(this);
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);

View File

@ -14,37 +14,35 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoPerformanceCalculator : PerformanceCalculator
{
protected new TaikoDifficultyAttributes Attributes => (TaikoDifficultyAttributes)base.Attributes;
private Mod[] mods;
private int countGreat;
private int countOk;
private int countMeh;
private int countMiss;
public TaikoPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
: base(ruleset, attributes, score)
public TaikoPerformanceCalculator(Ruleset ruleset)
: base(ruleset)
{
}
public override PerformanceAttributes Calculate()
protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
{
mods = Score.Mods;
countGreat = Score.Statistics.GetValueOrDefault(HitResult.Great);
countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
var taikoAttributes = (TaikoDifficultyAttributes)attributes;
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
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))
if (score.Mods.Any(m => m is ModNoFail))
multiplier *= 0.90;
if (mods.Any(m => m is ModHidden))
if (score.Mods.Any(m => m is ModHidden))
multiplier *= 1.10;
double difficultyValue = computeDifficultyValue();
double accuracyValue = computeAccuracyValue();
double difficultyValue = computeDifficultyValue(score, taikoAttributes);
double accuracyValue = computeAccuracyValue(score, taikoAttributes);
double totalValue =
Math.Pow(
Math.Pow(difficultyValue, 1.1) +
@ -59,30 +57,30 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
};
}
private double computeDifficultyValue()
private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
double difficultyValue = 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;
double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
difficultyValue *= lengthBonus;
difficultyValue *= Math.Pow(0.985, countMiss);
if (mods.Any(m => m is ModHidden))
if (score.Mods.Any(m => m is ModHidden))
difficultyValue *= 1.025;
if (mods.Any(m => m is ModFlashlight<TaikoHitObject>))
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
difficultyValue *= 1.05 * lengthBonus;
return difficultyValue * Score.Accuracy;
return difficultyValue * score.Accuracy;
}
private double computeAccuracyValue()
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
if (Attributes.GreatHitWindow <= 0)
if (attributes.GreatHitWindow <= 0)
return 0;
double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
double accValue = Math.Pow(150.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 15) * 22.0;
// 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));

View File

@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Taiko
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new TaikoPerformanceCalculator(this, attributes, score);
public override PerformanceCalculator CreatePerformanceCalculator() => new TaikoPerformanceCalculator(this);
public int LegacyID => 1;

View File

@ -390,7 +390,7 @@ namespace osu.Game.Tests.Visual.Ranking
private class RulesetWithNoPerformanceCalculator : OsuRuleset
{
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null;
public override PerformanceCalculator CreatePerformanceCalculator() => null;
}
}
}

View File

@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Difficulty
).ConfigureAwait(false);
// ScorePerformanceCache is not used to avoid caching multiple copies of essentially identical perfect performance attributes
return difficulty == null ? null : ruleset.CreatePerformanceCalculator(difficulty.Value.Attributes, perfectPlay)?.Calculate();
return difficulty == null ? null : ruleset.CreatePerformanceCalculator()?.Calculate(perfectPlay, difficulty.Value.Attributes);
}, cancellationToken);
}

View File

@ -1,41 +1,31 @@
// 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 System;
using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mods;
using osu.Game.Beatmaps;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Difficulty
{
public abstract class PerformanceCalculator
{
protected readonly DifficultyAttributes Attributes;
protected readonly Ruleset Ruleset;
protected readonly ScoreInfo Score;
protected double TimeRate { get; private set; } = 1;
protected PerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
protected PerformanceCalculator(Ruleset ruleset)
{
Ruleset = ruleset;
Score = score;
Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes));
ApplyMods(score.Mods);
}
protected virtual void ApplyMods(Mod[] mods)
{
var track = new TrackVirtual(10000);
mods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
TimeRate = track.Rate;
}
public PerformanceAttributes Calculate(ScoreInfo score, DifficultyAttributes attributes)
=> CreatePerformanceAttributes(score, attributes);
public abstract PerformanceAttributes Calculate();
public PerformanceAttributes Calculate(ScoreInfo score, IWorkingBeatmap beatmap)
=> Calculate(score, Ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods));
/// <summary>
/// Creates <see cref="PerformanceAttributes"/> to describe a score's performance.
/// </summary>
/// <param name="score">The score to create the attributes for.</param>
/// <param name="attributes">The difficulty attributes for the beatmap relating to the score.</param>
protected abstract PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes);
}
}

View File

@ -228,25 +228,9 @@ namespace osu.Game.Rulesets
/// <summary>
/// Optionally creates a <see cref="PerformanceCalculator"/> to generate performance data from the provided score.
/// </summary>
/// <param name="attributes">Difficulty attributes for the beatmap related to the provided score.</param>
/// <param name="score">The score to be processed.</param>
/// <returns>A performance calculator instance for the provided score.</returns>
[CanBeNull]
public virtual PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => null;
/// <summary>
/// Optionally creates a <see cref="PerformanceCalculator"/> to generate performance data from the provided score.
/// </summary>
/// <param name="beatmap">The beatmap to use as a source for generating <see cref="DifficultyAttributes"/>.</param>
/// <param name="score">The score to be processed.</param>
/// <returns>A performance calculator instance for the provided score.</returns>
[CanBeNull]
public PerformanceCalculator CreatePerformanceCalculator(IWorkingBeatmap beatmap, ScoreInfo score)
{
var difficultyCalculator = CreateDifficultyCalculator(beatmap);
var difficultyAttributes = difficultyCalculator.Calculate(score.Mods);
return CreatePerformanceCalculator(difficultyAttributes, score);
}
public virtual PerformanceCalculator CreatePerformanceCalculator() => null;
public virtual HitObjectComposer CreateHitObjectComposer() => null;

View File

@ -43,9 +43,7 @@ namespace osu.Game.Scoring
token.ThrowIfCancellationRequested();
var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Value.Attributes, score);
return calculator?.Calculate();
return score.Ruleset.CreateInstance().CreatePerformanceCalculator()?.Calculate(score, attributes.Value.Attributes);
}
public readonly struct PerformanceCacheLookup

View File

@ -130,9 +130,9 @@ namespace osu.Game.Screens.Play.HUD
var scoreInfo = gameplayState.Score.ScoreInfo.DeepClone();
scoreInfo.Mods = clonedMods;
var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, scoreInfo);
var calculator = gameplayState.Ruleset.CreatePerformanceCalculator();
Current.Value = (int)Math.Round(calculator?.Calculate().Total ?? 0, MidpointRounding.AwayFromZero);
Current.Value = (int)Math.Round(calculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero);
IsValid = true;
}