mirror of
https://github.com/osukey/osukey.git
synced 2025-07-02 00:40:09 +09:00
Restructure PerformanceCalculator to not require ScoreInfo argument
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user