private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes) { if (!score.Mods.Any(h => h is OsuModFlashlight)) { return(0.0); } double rawFlashlight = attributes.FlashlightDifficulty; if (score.Mods.Any(m => m is OsuModTouchDevice)) { rawFlashlight = Math.Pow(rawFlashlight, 0.8); } double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0; // 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(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) + (totalHits > 200 ? 0.2 * Math.Min(1.0, (totalHits - 200) / 200.0) : 0.0); // 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; return(flashlightValue); }
private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes attributes) { 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; if (amountHitObjectsWithAccuracy > 0) { betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); } else { betterAccuracyPercentage = 0; } // It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points. if (betterAccuracyPercentage < 0) { betterAccuracyPercentage = 0; } // 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; // 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 (score.Mods.Any(m => m is OsuModBlinds)) { accuracyValue *= 1.14; } else if (score.Mods.Any(m => m is OsuModHidden)) { accuracyValue *= 1.08; } if (score.Mods.Any(m => m is OsuModFlashlight)) { accuracyValue *= 1.02; } return(accuracyValue); }
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 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; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) { speedValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); } speedValue *= getComboScalingFactor(attributes); double approachRateFactor = 0.0; 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 (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 (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); } // 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); // Scale the speed value with # of 50s to punish doubletapping. speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); return(speedValue); }
private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) { // Guess the number of misses + slider breaks from combo double comboBasedMissCount = 0.0; if (attributes.SliderCount > 0) { double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount; if (scoreMaxCombo < fullComboThreshold) { comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); } } // Clamp miss count since it's derived from combo and can be higher than total hits and that breaks some calculations comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits); return(Math.Max(countMiss, comboBasedMissCount)); }
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes) { double rawAim = attributes.AimDifficulty; 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; 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(effectiveMissCount / totalHits, 0.775), effectiveMissCount); } 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); } aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. 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); } // 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; 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; aimValue *= sliderNerfFactor; } aimValue *= accuracy; // It is important to consider accuracy difficulty when scaling with accuracy. aimValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; return(aimValue); }
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);