mirror of
https://github.com/ppy/osu.git
synced 2025-01-31 05:23:21 +08:00
Deal with accidental merge issues
why does merging have to be so difficult
This commit is contained in:
parent
984af6907c
commit
b8fa87a605
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -13,7 +14,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
{
|
{
|
||||||
public class OsuPerformanceCalculator : PerformanceCalculator
|
public class OsuPerformanceCalculator : PerformanceCalculator
|
||||||
{
|
{
|
||||||
public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
public const double PERFORMANCE_BASE_MULTIPLIER = 1.15; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||||
|
|
||||||
|
private bool usingClassicSliderAccuracy;
|
||||||
|
|
||||||
private double accuracy;
|
private double accuracy;
|
||||||
private int scoreMaxCombo;
|
private int scoreMaxCombo;
|
||||||
@ -22,6 +25,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
private int countMeh;
|
private int countMeh;
|
||||||
private int countMiss;
|
private int countMiss;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Missed slider ticks that includes missed reverse arrows. Will only be correct on non-classic scores
|
||||||
|
/// </summary>
|
||||||
|
private int countSliderTickMiss;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Amount of missed slider tails that don't break combo. Will only be correct on non-classic scores
|
||||||
|
/// </summary>
|
||||||
|
private int countSliderEndsDropped;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estimated total amount of combo breaks
|
||||||
|
/// </summary>
|
||||||
private double effectiveMissCount;
|
private double effectiveMissCount;
|
||||||
|
|
||||||
public OsuPerformanceCalculator()
|
public OsuPerformanceCalculator()
|
||||||
@ -33,13 +49,45 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
{
|
{
|
||||||
var osuAttributes = (OsuDifficultyAttributes)attributes;
|
var osuAttributes = (OsuDifficultyAttributes)attributes;
|
||||||
|
|
||||||
|
usingClassicSliderAccuracy = score.Mods.OfType<OsuModClassic>().Any(m => m.NoSliderHeadAccuracy.Value);
|
||||||
|
|
||||||
scoreMaxCombo = score.MaxCombo;
|
scoreMaxCombo = score.MaxCombo;
|
||||||
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
|
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||||
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||||
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
effectiveMissCount = calculateEffectiveMissCount(osuAttributes);
|
countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit);
|
||||||
accuracy = calculateEffectiveAccuracy(countGreat, countOk, countMeh, countMiss, totalHits);
|
countSliderTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss);
|
||||||
|
|
||||||
|
accuracy = calculateAccuracy(countGreat, countOk, countMeh, countMiss, totalHits);
|
||||||
|
|
||||||
|
if (osuAttributes.SliderCount > 0)
|
||||||
|
{
|
||||||
|
if (usingClassicSliderAccuracy)
|
||||||
|
{
|
||||||
|
// Consider that full combo is maximum combo minus dropped slider tails since they don't contribute to combo but also don't break it
|
||||||
|
// In classic scores we can't know the amount of dropped sliders so we estimate to 10% of all sliders on the map
|
||||||
|
double fullComboThreshold = attributes.MaxCombo - 0.1 * osuAttributes.SliderCount;
|
||||||
|
|
||||||
|
if (scoreMaxCombo < fullComboThreshold)
|
||||||
|
effectiveMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
||||||
|
|
||||||
|
// In classic scores there can't be more misses than a sum of all non-perfect judgements
|
||||||
|
effectiveMissCount = Math.Min(effectiveMissCount, totalImperfectHits);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double fullComboThreshold = attributes.MaxCombo - countSliderEndsDropped;
|
||||||
|
|
||||||
|
if (scoreMaxCombo < fullComboThreshold)
|
||||||
|
effectiveMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
||||||
|
|
||||||
|
// Combine regular misses with tick misses since tick misses break combo as well
|
||||||
|
effectiveMissCount = Math.Min(effectiveMissCount, countSliderTickMiss + countMiss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
effectiveMissCount = Math.Max(countMiss, effectiveMissCount);
|
||||||
|
|
||||||
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
||||||
|
|
||||||
@ -86,17 +134,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||||
{
|
{
|
||||||
double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
double aimValue = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty);
|
||||||
|
|
||||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.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);
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||||
aimValue *= lengthBonus;
|
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)
|
if (effectiveMissCount > 0)
|
||||||
aimValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount);
|
aimValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount);
|
||||||
|
|
||||||
aimValue *= getComboScalingFactor(attributes);
|
|
||||||
|
|
||||||
double approachRateFactor = 0.0;
|
double approachRateFactor = 0.0;
|
||||||
if (attributes.ApproachRate > 10.33)
|
if (attributes.ApproachRate > 10.33)
|
||||||
@ -111,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
if (score.Mods.Any(m => m is OsuModBlinds))
|
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);
|
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))
|
else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable))
|
||||||
{
|
{
|
||||||
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
// 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);
|
||||||
@ -122,8 +167,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
if (attributes.SliderCount > 0)
|
if (attributes.SliderCount > 0)
|
||||||
{
|
{
|
||||||
double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
|
double estimateImproperlyFollowedDifficultSliders;
|
||||||
double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor;
|
|
||||||
|
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.Min(countSliderEndsDropped + countSliderTickMiss, estimateDifficultSliders);
|
||||||
|
}
|
||||||
|
|
||||||
|
double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateImproperlyFollowedDifficultSliders / estimateDifficultSliders, 3) + attributes.SliderFactor;
|
||||||
aimValue *= sliderNerfFactor;
|
aimValue *= sliderNerfFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,17 +198,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
if (score.Mods.Any(h => h is OsuModRelax))
|
if (score.Mods.Any(h => h is OsuModRelax))
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
|
||||||
double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
|
||||||
|
|
||||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.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);
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||||
speedValue *= lengthBonus;
|
speedValue *= lengthBonus;
|
||||||
|
|
||||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
|
||||||
if (effectiveMissCount > 0)
|
if (effectiveMissCount > 0)
|
||||||
speedValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
|
speedValue *= calculateMissPenalty(effectiveMissCount, attributes.SpeedDifficultStrainCount);
|
||||||
|
|
||||||
speedValue *= getComboScalingFactor(attributes);
|
|
||||||
|
|
||||||
double approachRateFactor = 0.0;
|
double approachRateFactor = 0.0;
|
||||||
if (attributes.ApproachRate > 10.33)
|
if (attributes.ApproachRate > 10.33)
|
||||||
@ -162,7 +218,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
// Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given.
|
// Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given.
|
||||||
speedValue *= 1.12;
|
speedValue *= 1.12;
|
||||||
}
|
}
|
||||||
else if (score.Mods.Any(m => m is OsuModHidden))
|
else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable))
|
||||||
{
|
{
|
||||||
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
// 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);
|
||||||
@ -174,10 +230,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat));
|
double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat));
|
||||||
double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk));
|
double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk));
|
||||||
double relevantCountMiss = Math.Max(0, countMiss - Math.Max(0, relevantTotalDiff - countGreat - countOk - countMeh));
|
double relevantCountMiss = Math.Max(0, countMiss - Math.Max(0, relevantTotalDiff - countGreat - countOk - countMeh));
|
||||||
double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : calculateEffectiveAccuracy(relevantCountGreat, relevantCountOk, relevantCountMeh, relevantCountMiss, attributes.SpeedNoteCount);
|
double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : calculateAccuracy(relevantCountGreat, relevantCountOk, relevantCountMeh, relevantCountMiss, attributes.SpeedNoteCount);
|
||||||
|
|
||||||
// Scale the speed value with accuracy and OD.
|
// Scale the speed value with accuracy and OD.
|
||||||
speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
|
speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - attributes.OverallDifficulty) / 2);
|
||||||
|
|
||||||
// Scale the speed value with # of 50s to punish doubletapping.
|
// Scale the speed value with # of 50s to punish doubletapping.
|
||||||
speedValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
speedValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||||
@ -193,9 +249,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
|
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
|
||||||
double betterAccuracyPercentage;
|
double betterAccuracyPercentage;
|
||||||
int amountHitObjectsWithAccuracy = attributes.HitCircleCount;
|
int amountHitObjectsWithAccuracy = attributes.HitCircleCount;
|
||||||
|
if (!usingClassicSliderAccuracy)
|
||||||
|
amountHitObjectsWithAccuracy += attributes.SliderCount;
|
||||||
|
|
||||||
if (amountHitObjectsWithAccuracy > 0)
|
if (amountHitObjectsWithAccuracy > 0)
|
||||||
betterAccuracyPercentage = calculateEffectiveAccuracy(countGreat - (totalHits - amountHitObjectsWithAccuracy), countOk, countMeh, countMiss, amountHitObjectsWithAccuracy);
|
betterAccuracyPercentage = calculateAccuracy(countGreat - (totalHits - amountHitObjectsWithAccuracy), countOk, countMeh, countMiss, amountHitObjectsWithAccuracy);
|
||||||
else
|
else
|
||||||
betterAccuracyPercentage = 0;
|
betterAccuracyPercentage = 0;
|
||||||
|
|
||||||
@ -213,7 +271,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
// Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given.
|
// Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given.
|
||||||
if (score.Mods.Any(m => m is OsuModBlinds))
|
if (score.Mods.Any(m => m is OsuModBlinds))
|
||||||
accuracyValue *= 1.14;
|
accuracyValue *= 1.14;
|
||||||
else if (score.Mods.Any(m => m is OsuModHidden))
|
else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable))
|
||||||
accuracyValue *= 1.08;
|
accuracyValue *= 1.08;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is OsuModFlashlight))
|
if (score.Mods.Any(m => m is OsuModFlashlight))
|
||||||
@ -227,7 +285,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
if (!score.Mods.Any(h => h is OsuModFlashlight))
|
if (!score.Mods.Any(h => h is OsuModFlashlight))
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
|
||||||
double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0;
|
double flashlightValue = Flashlight.DifficultyToPerformance(attributes.FlashlightDifficulty);
|
||||||
|
|
||||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||||
if (effectiveMissCount > 0)
|
if (effectiveMissCount > 0)
|
||||||
@ -247,26 +305,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
return flashlightValue;
|
return flashlightValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes)
|
// Miss penalty assumes that a player will miss on the hardest parts of a map,
|
||||||
{
|
// so we use the amount of relatively difficult sections to adjust miss penalty
|
||||||
// Guess the number of misses + slider breaks from combo
|
// to make it more punishing on maps with lower amount of hard sections.
|
||||||
double comboBasedMissCount = 0.0;
|
private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.96 / ((missCount / (4 * Math.Pow(Math.Log(difficultStrainCount), 0.94))) + 1);
|
||||||
|
|
||||||
if (attributes.SliderCount > 0)
|
private double calculateAccuracy(double countGreat, double countOk, double countMeh, double countMiss, double totalHits)
|
||||||
{
|
=> usingClassicSliderAccuracy
|
||||||
double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount;
|
? (countGreat * 6 + countOk * 2 + countMeh + countMiss * 2) / (totalHits * 6)
|
||||||
if (scoreMaxCombo < fullComboThreshold)
|
: (countGreat * 6 + countOk * 2 + countMeh) / ((totalHits - countMiss) * 6);
|
||||||
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp miss count to maximum amount of possible breaks
|
|
||||||
comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss);
|
|
||||||
|
|
||||||
return Math.Max(countMiss, comboBasedMissCount);
|
|
||||||
}
|
|
||||||
private double calculateEffectiveAccuracy(double countGreat, double countOk, double countMeh, double countMiss, double totalHits) => (countGreat * 6 + countOk * 2 + countMeh + countMiss * 2) / (double)(totalHits * 6);
|
|
||||||
|
|
||||||
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 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 totalHits => countGreat + countOk + countMeh + countMiss;
|
||||||
|
private int totalImperfectHits => countOk + countMeh + countMiss;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user