mirror of
https://github.com/ppy/osu.git
synced 2025-03-14 05:47:20 +08:00
cutting only low AR
This commit is contained in:
parent
d50df4e95d
commit
325e18da9a
@ -165,94 +165,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
return Math.Max(0, Math.Pow(difficulty, 1.5) - 1);
|
||||
}
|
||||
|
||||
// Returns value from 0 to 1, where 0 is very predictable and 1 is very unpredictable
|
||||
public static double EvaluateInpredictabilityOf(DifficultyHitObject current)
|
||||
{
|
||||
// make the sum equal to 1
|
||||
const double velocity_change_part = 0.8;
|
||||
const double angle_change_part = 0.1;
|
||||
const double rhythm_change_part = 0.1;
|
||||
|
||||
if (current.BaseObject is Spinner || current.Index == 0 || current.Previous(0).BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||
var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
|
||||
|
||||
// Rhythm difference punishment for velocity and angle bonuses
|
||||
double rhythmSimilarity = 1 - getRhythmDifference(osuCurrObj.StrainTime, osuLastObj.StrainTime);
|
||||
|
||||
// Make differentiation going from 1/4 to 1/2 and bigger difference
|
||||
// To 1/3 to 1/2 and smaller difference
|
||||
rhythmSimilarity = Math.Clamp(rhythmSimilarity, 0.5, 0.75);
|
||||
rhythmSimilarity = 4 * (rhythmSimilarity - 0.5);
|
||||
|
||||
double velocityChangeBonus = getVelocityChangeFactor(osuCurrObj, osuLastObj) * rhythmSimilarity;
|
||||
|
||||
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
|
||||
double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime;
|
||||
|
||||
double angleChangeBonus = 0;
|
||||
|
||||
if (osuCurrObj.Angle != null && osuLastObj.Angle != null && currVelocity > 0 && prevVelocity > 0)
|
||||
{
|
||||
angleChangeBonus = 1 - osuCurrObj.AnglePredictability;
|
||||
angleChangeBonus *= Math.Min(currVelocity, prevVelocity) / Math.Max(currVelocity, prevVelocity); // Prevent cheesing
|
||||
}
|
||||
|
||||
angleChangeBonus *= rhythmSimilarity;
|
||||
|
||||
// This bonus only awards rhythm changes if they're not filled with sliderends
|
||||
double rhythmChangeBonus = 0;
|
||||
|
||||
if (current.Index > 1)
|
||||
{
|
||||
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
|
||||
|
||||
double currDelta = osuCurrObj.StrainTime;
|
||||
double lastDelta = osuLastObj.StrainTime;
|
||||
|
||||
if (osuLastObj.BaseObject is Slider sliderCurr)
|
||||
{
|
||||
currDelta -= sliderCurr.Duration / osuCurrObj.ClockRate;
|
||||
currDelta = Math.Max(0, currDelta);
|
||||
}
|
||||
|
||||
if (osuLastLastObj.BaseObject is Slider sliderLast)
|
||||
{
|
||||
lastDelta -= sliderLast.Duration / osuLastObj.ClockRate;
|
||||
lastDelta = Math.Max(0, lastDelta);
|
||||
}
|
||||
|
||||
rhythmChangeBonus = getRhythmDifference(currDelta, lastDelta);
|
||||
}
|
||||
|
||||
double result = velocity_change_part * velocityChangeBonus + angle_change_part * angleChangeBonus + rhythm_change_part * rhythmChangeBonus;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double getVelocityChangeFactor(OsuDifficultyHitObject osuCurrObj, OsuDifficultyHitObject osuLastObj)
|
||||
{
|
||||
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
|
||||
double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime;
|
||||
|
||||
double velocityChangeFactor = 0;
|
||||
|
||||
// https://www.desmos.com/calculator/kqxmqc8pkg
|
||||
if (currVelocity > 0 || prevVelocity > 0)
|
||||
{
|
||||
double velocityChange = Math.Max(0,
|
||||
Math.Min(
|
||||
Math.Abs(prevVelocity - currVelocity) - 0.5 * Math.Min(currVelocity, prevVelocity),
|
||||
Math.Max(((OsuHitObject)osuCurrObj.BaseObject).Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Min(currVelocity, prevVelocity))
|
||||
)); // Stealed from xexxar
|
||||
velocityChangeFactor = velocityChange / Math.Max(currVelocity, prevVelocity); // maxiumum is 0.4
|
||||
velocityChangeFactor /= 0.4;
|
||||
}
|
||||
|
||||
return velocityChangeFactor;
|
||||
}
|
||||
|
||||
private static double getTimeNerfFactor(double deltaTime) => Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1);
|
||||
private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2);
|
||||
private static double logistic(double x) => 1 / (1 + Math.Exp(-x));
|
||||
@ -282,73 +194,4 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
return arr[result].Overlapness;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReadingHiddenEvaluator
|
||||
{
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current)
|
||||
{
|
||||
var currObj = (OsuDifficultyHitObject)current;
|
||||
|
||||
double density = ReadingEvaluator.EvaluateDensityOf(current, false);
|
||||
double preempt = currObj.Preempt / 1000;
|
||||
|
||||
double densityFactor = Math.Pow(density / 6.2, 1.5);
|
||||
|
||||
double invisibilityFactor;
|
||||
|
||||
// AR11+DT and faster = 0 HD pp unless density is big
|
||||
if (preempt < 0.2) invisibilityFactor = 0;
|
||||
|
||||
// Else accelerating growth until around ART0, then linear, and starting from AR5 is 3 times faster again to buff AR0 +HD
|
||||
else invisibilityFactor = Math.Min(Math.Pow(preempt * 2.4 - 0.2, 5), Math.Max(preempt, preempt * 3 - 2.4));
|
||||
|
||||
|
||||
double hdDifficulty = invisibilityFactor + densityFactor;
|
||||
|
||||
// Scale by inpredictability slightly
|
||||
hdDifficulty *= 0.96 + 0.1 * ReadingEvaluator.EvaluateInpredictabilityOf(current); // Max multiplier is 1.1
|
||||
|
||||
return hdDifficulty;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReadingHighAREvaluator
|
||||
{
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, bool applyAdjust = false)
|
||||
{
|
||||
var currObj = (OsuDifficultyHitObject)current;
|
||||
|
||||
double result = GetDifficulty(currObj.Preempt);
|
||||
|
||||
if (applyAdjust)
|
||||
{
|
||||
double inpredictability = ReadingEvaluator.EvaluateInpredictabilityOf(current);
|
||||
|
||||
// follow lines make high AR easier, so apply nerf if object isn't new combo
|
||||
inpredictability *= 1 + 0.1 * (800 - currObj.FollowLineTime) / 800;
|
||||
|
||||
result *= 0.98 + 0.6 * inpredictability;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// High AR curve
|
||||
// https://www.desmos.com/calculator/srzbeumngi
|
||||
public static double GetDifficulty(double preempt)
|
||||
{
|
||||
// Get preempt in seconds
|
||||
preempt /= 1000;
|
||||
double value;
|
||||
|
||||
if (preempt < 0.375) // We have stop in the point of AR10.5, the value here = 0.396875, derivative = -10.5833,
|
||||
value = 0.63 * Math.Pow(8 - 20 * preempt, 2.0 / 3); // This function is matching live high AR bonus
|
||||
else
|
||||
value = Math.Exp(9.07583 - 80.0 * preempt / 3);
|
||||
|
||||
// The power is 2 times higher to compensate sqrt in high AR skill
|
||||
// EDIT: looks like AR11 getting a bit overnerfed in comparison to other ARs, so i will increase the difference
|
||||
return Math.Pow(value, 2.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,24 +38,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
[JsonProperty("reading_low_ar_difficulty")]
|
||||
public double ReadingDifficultyLowAR { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The difficulty corresponding to the reading skill. High AR branch.
|
||||
/// </summary>
|
||||
[JsonProperty("reading_high_ar_difficulty")]
|
||||
public double ReadingDifficultyHighAR { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The difficulty corresponding to the reading skill. Sliders branch.
|
||||
/// </summary>
|
||||
[JsonProperty("reading_sliders_difficulty")]
|
||||
public double ReadingDifficultySliders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The difficulty corresponding to the reading skill. Hidden mod branch.
|
||||
/// </summary>
|
||||
[JsonProperty("reading_hidden_difficulty")]
|
||||
public double HiddenDifficulty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The difficulty corresponding to the flashlight skill.
|
||||
/// </summary>
|
||||
|
@ -44,17 +44,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double hiddenFlashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
|
||||
|
||||
double readingLowARRating = Math.Sqrt(skills[4].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
|
||||
double readingHighARRating = Math.Sqrt(skills[5].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
|
||||
|
||||
double hiddenRating = 0;
|
||||
double flashlightRating = 0;
|
||||
|
||||
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
||||
|
||||
double flashlightRating = 0;
|
||||
double baseFlashlightPerformance = 0.0;
|
||||
if (mods.Any(h => h is OsuModFlashlight))
|
||||
{
|
||||
flashlightRating = Math.Sqrt(skills[5].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
|
||||
baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating);
|
||||
}
|
||||
|
||||
if (mods.Any(m => m is OsuModTouchDevice))
|
||||
{
|
||||
aimRating = Math.Pow(aimRating, 0.8);
|
||||
flashlightRating = Math.Pow(flashlightRating, 0.8);
|
||||
readingLowARRating = Math.Pow(readingLowARRating, 0.9);
|
||||
}
|
||||
|
||||
if (mods.Any(h => h is OsuModRelax))
|
||||
@ -62,32 +67,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
aimRating *= 0.9;
|
||||
speedRating = 0.0;
|
||||
flashlightRating *= 0.7;
|
||||
readingLowARRating *= 0.95;
|
||||
}
|
||||
|
||||
double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating);
|
||||
double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating);
|
||||
|
||||
// Cognition
|
||||
int flIndex = 6;
|
||||
|
||||
double baseReadingHiddenPerformance = 0;
|
||||
if (mods.Any(h => h is OsuModHidden))
|
||||
{
|
||||
hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
|
||||
baseReadingHiddenPerformance = ReadingHidden.DifficultyToPerformance(hiddenRating);
|
||||
flIndex++;
|
||||
}
|
||||
|
||||
double baseFlashlightPerformance = 0.0;
|
||||
if (mods.Any(h => h is OsuModFlashlight))
|
||||
{
|
||||
flashlightRating = Math.Sqrt(skills[flIndex].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
|
||||
baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating);
|
||||
}
|
||||
|
||||
double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating);
|
||||
double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating);
|
||||
double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SUM_POWER) + Math.Pow(baseReadingHighARPerformance, SUM_POWER), 1.0 / SUM_POWER);
|
||||
double baseReadingARPerformance = baseReadingLowARPerformance;
|
||||
|
||||
double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FL_SUM_POWER) + Math.Pow(baseReadingARPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER);
|
||||
|
||||
@ -100,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
int sliderCount = beatmap.HitObjects.Count(h => h is Slider);
|
||||
int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner);
|
||||
|
||||
double cognitionPerformance = baseFlashlightARPerformance + baseReadingHiddenPerformance;
|
||||
double cognitionPerformance = baseFlashlightARPerformance;
|
||||
double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SUM_POWER) + Math.Pow(baseSpeedPerformance, SUM_POWER), 1.0 / SUM_POWER);
|
||||
|
||||
// Limit cognition by full memorisation difficulty
|
||||
@ -126,8 +114,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
SpeedDifficulty = speedRating,
|
||||
SpeedNoteCount = speedNotes,
|
||||
ReadingDifficultyLowAR = readingLowARRating,
|
||||
ReadingDifficultyHighAR = readingHighARRating,
|
||||
HiddenDifficulty = hiddenRating,
|
||||
FlashlightDifficulty = flashlightRating,
|
||||
HiddenFlashlightDifficulty = hiddenFlashlightRating,
|
||||
SliderFactor = sliderFactor,
|
||||
@ -167,12 +153,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
new Speed(mods),
|
||||
new HiddenFlashlight(mods),
|
||||
new ReadingLowAR(mods),
|
||||
new ReadingHighAR(mods),
|
||||
};
|
||||
|
||||
if (mods.Any(h => h is OsuModHidden))
|
||||
skills.Add(new ReadingHidden(mods));
|
||||
|
||||
if (mods.Any(h => h is OsuModFlashlight))
|
||||
skills.Add(new Flashlight(mods));
|
||||
|
||||
|
@ -77,23 +77,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double potentialHiddenFlashlightValue = computeFlashlightValue(score, osuAttributes, true);
|
||||
|
||||
double lowARValue = computeReadingLowARValue(score, osuAttributes);
|
||||
double highARValue = computeReadingHighARValue(score, osuAttributes);
|
||||
|
||||
double readingARValue = Math.Pow(Math.Pow(lowARValue, power) + Math.Pow(highARValue, power), 1.0 / power);
|
||||
double readingARValue = lowARValue;
|
||||
|
||||
double flashlightValue = 0;
|
||||
if (score.Mods.Any(h => h is OsuModFlashlight))
|
||||
flashlightValue = computeFlashlightValue(score, osuAttributes);
|
||||
|
||||
double readingHDValue = 0;
|
||||
if (score.Mods.Any(h => h is OsuModHidden))
|
||||
readingHDValue = computeReadingHiddenValue(score, osuAttributes);
|
||||
|
||||
// Reduce AR reading bonus if FL is present
|
||||
double flPower = OsuDifficultyCalculator.FL_SUM_POWER;
|
||||
double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, flPower) + Math.Pow(readingARValue, flPower), 1.0 / flPower);
|
||||
|
||||
double cognitionValue = flashlightARValue + readingHDValue;
|
||||
double cognitionValue = flashlightARValue;
|
||||
cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialHiddenFlashlightValue);
|
||||
|
||||
double accuracyValue = computeAccuracyValue(score, osuAttributes);
|
||||
@ -129,9 +124,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
aimValue *= getComboScalingFactor(attributes);
|
||||
|
||||
double approachRateFactor = 0.0;
|
||||
if (attributes.ApproachRate > 10.33)
|
||||
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
|
||||
|
||||
if (score.Mods.Any(h => h is OsuModRelax))
|
||||
approachRateFactor = 0.0;
|
||||
|
||||
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(m => m is OsuModTraceable))
|
||||
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.
|
||||
aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
|
||||
@ -170,12 +174,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
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 OsuModTraceable))
|
||||
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.
|
||||
speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
|
||||
@ -225,6 +235,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
// 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 || m is OsuModTraceable))
|
||||
accuracyValue *= 1.08;
|
||||
|
||||
if (score.Mods.Any(m => m is OsuModFlashlight))
|
||||
accuracyValue *= 1.02;
|
||||
@ -239,8 +251,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double deltaBonus = (1 - Math.Pow(0.95, Math.Pow(ARODDelta, 4)));
|
||||
|
||||
accuracyValue *= 1 + visualBonus * (1 + 2 * deltaBonus);
|
||||
if (score.Mods.Any(h => h is OsuModHidden || h is OsuModTraceable))
|
||||
accuracyValue *= 1 + visualBonus * (1 + deltaBonus);
|
||||
|
||||
return accuracyValue;
|
||||
}
|
||||
@ -299,86 +309,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
return readingValue;
|
||||
}
|
||||
|
||||
private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||
{
|
||||
double highARValue = OsuStrainSkill.DifficultyToPerformance(attributes.ReadingDifficultyHighAR);
|
||||
|
||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||
if (effectiveMissCount > 0)
|
||||
highARValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount);
|
||||
|
||||
highARValue *= getComboScalingFactor(attributes);
|
||||
|
||||
// Approximate how much of high AR difficulty is aim
|
||||
double aimPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty);
|
||||
double speedPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
|
||||
|
||||
double aimRatio = aimPerformance / (aimPerformance + speedPerformance);
|
||||
|
||||
// Aim part calculation
|
||||
double aimPartValue = highARValue * aimRatio;
|
||||
{
|
||||
// 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;
|
||||
aimPartValue *= sliderNerfFactor;
|
||||
}
|
||||
|
||||
aimPartValue *= accuracy;
|
||||
// It is important to consider accuracy difficulty when scaling with accuracy.
|
||||
aimPartValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500;
|
||||
}
|
||||
|
||||
// Speed part calculation
|
||||
double speedPartValue = highARValue * (1 - aimRatio);
|
||||
{
|
||||
// Calculate accuracy assuming the worst case scenario
|
||||
double relevantTotalDiff = totalHits - attributes.SpeedNoteCount;
|
||||
double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff);
|
||||
double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat));
|
||||
double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk));
|
||||
double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0);
|
||||
|
||||
// Scale the speed value with accuracy and OD.
|
||||
speedPartValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
|
||||
|
||||
// Scale the speed value with # of 50s to punish doubletapping.
|
||||
speedPartValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||
}
|
||||
|
||||
return aimPartValue + speedPartValue;
|
||||
}
|
||||
|
||||
private double computeReadingHiddenValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||
{
|
||||
if (!score.Mods.Any(h => h is OsuModHidden))
|
||||
return 0.0;
|
||||
|
||||
double rawReading = attributes.HiddenDifficulty;
|
||||
double hiddenValue = ReadingHidden.DifficultyToPerformance(attributes.HiddenDifficulty);
|
||||
|
||||
double lengthBonus = CalculateDefaultLengthBonus(totalHits);
|
||||
hiddenValue *= lengthBonus;
|
||||
|
||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||
if (effectiveMissCount > 0)
|
||||
hiddenValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
|
||||
|
||||
hiddenValue *= getComboScalingFactor(attributes);
|
||||
|
||||
// Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework.
|
||||
hiddenValue *= accuracy * accuracy;
|
||||
// It is important to also consider accuracy difficulty when doing that.
|
||||
hiddenValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500;
|
||||
|
||||
return hiddenValue;
|
||||
}
|
||||
|
||||
private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes)
|
||||
{
|
||||
// Guess the number of misses + slider breaks from combo
|
||||
|
@ -98,11 +98,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
/// </summary>
|
||||
public double HitWindowGreat { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rhythm difficulty of the object. Saved for optimization, rhythm calculation is very expensive.
|
||||
/// </summary>
|
||||
public double RhythmDifficulty { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Density of the object for given preempt. Saved for optimization, density calculation is expensive.
|
||||
/// </summary>
|
||||
@ -128,18 +123,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
/// </summary>
|
||||
public readonly double Preempt;
|
||||
|
||||
/// <summary>
|
||||
/// Preempt of follow line for this <see cref="OsuDifficultyHitObject"/> adjusted by clockrate.
|
||||
/// Will be equal to 0 if object is New Combo.
|
||||
/// </summary>
|
||||
public readonly double FollowLineTime;
|
||||
|
||||
/// <summary>
|
||||
/// Playback rate of beatmap.
|
||||
/// Will be equal 1.5 on DT and 0.75 on HT.
|
||||
/// </summary>
|
||||
public readonly double ClockRate;
|
||||
|
||||
private readonly OsuHitObject? lastLastObject;
|
||||
private readonly OsuHitObject lastObject;
|
||||
|
||||
@ -152,9 +135,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
StackedPosition = currObject.StackedPosition;
|
||||
Preempt = BaseObject.TimePreempt / clockRate;
|
||||
FollowLineTime = 800 / clockRate; // 800ms is follow line appear time
|
||||
FollowLineTime *= (currObject.NewCombo ? 0 : 1); // no follow lines when NC
|
||||
ClockRate = clockRate;
|
||||
|
||||
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||
StrainTime = Math.Max(DeltaTime, min_delta_time);
|
||||
@ -174,7 +154,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
(ReadingObjects, OverlapValues) = getReadingObjects();
|
||||
|
||||
RhythmDifficulty = RhythmEvaluator.EvaluateDifficultyOf(this);
|
||||
Density = ReadingEvaluator.EvaluateDensityOf(this);
|
||||
}
|
||||
|
||||
|
@ -21,17 +21,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
|
||||
private readonly bool withSliders;
|
||||
|
||||
protected double CurrentStrain;
|
||||
protected double SkillMultiplier => 23.55;
|
||||
private double currentStrain;
|
||||
|
||||
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => CurrentStrain * StrainDecay(time - current.Previous(0).StartTime);
|
||||
private double skillMultiplier => 23.55;
|
||||
private double strainDecayBase => 0.15;
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||
|
||||
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime);
|
||||
|
||||
protected override double StrainValueAt(DifficultyHitObject current)
|
||||
{
|
||||
CurrentStrain *= StrainDecay(current.DeltaTime);
|
||||
CurrentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * SkillMultiplier;
|
||||
currentStrain *= strainDecay(current.DeltaTime);
|
||||
currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier;
|
||||
|
||||
return CurrentStrain;
|
||||
return currentStrain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
hasHiddenMod = mods.Any(m => m is OsuModHidden);
|
||||
}
|
||||
|
||||
private double skillMultiplier => 0.05;
|
||||
private double skillMultiplier => 0.052;
|
||||
private double strainDecayBase => 0.15;
|
||||
|
||||
private double currentStrain;
|
||||
|
@ -34,10 +34,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// </summary>
|
||||
protected virtual double DifficultyMultiplier => DEFAULT_DIFFICULTY_MULTIPLIER;
|
||||
|
||||
protected virtual double StrainDecayBase => 0.15;
|
||||
|
||||
protected double StrainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
|
||||
|
||||
protected OsuStrainSkill(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
@ -76,10 +72,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// Converts difficulty value from <see cref="OsuDifficultyAttributes"/> to base performance.
|
||||
/// </summary>
|
||||
public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
|
||||
/// <summary>
|
||||
/// Converts base performance to <see cref="OsuDifficultyAttributes"/> difficulty value.s
|
||||
/// </summary>
|
||||
public static double PerformanceToDifficulty(double performance) => (Math.Pow(100000.0 * performance, 1.0 / 3.0) + 4.0) / 5.0 * 0.0675;
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,11 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
|
||||
public class ReadingLowAR : GraphSkill
|
||||
public class ReadingLowAR : Skill
|
||||
{
|
||||
private readonly List<double> difficulties = new List<double>();
|
||||
private double skillMultiplier => 1.26;
|
||||
@ -42,18 +40,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
double totalDensityDifficulty = (currentDensityAimStrain + densityReadingDifficulty) * skillMultiplier;
|
||||
|
||||
difficulties.Add(totalDensityDifficulty);
|
||||
|
||||
if (current.Index == 0)
|
||||
CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
|
||||
|
||||
while (current.StartTime > CurrentSectionEnd)
|
||||
{
|
||||
StrainPeaks.Add(CurrentSectionPeak);
|
||||
CurrentSectionPeak = 0;
|
||||
CurrentSectionEnd += SectionLength;
|
||||
}
|
||||
|
||||
CurrentSectionPeak = Math.Max(totalDensityDifficulty, CurrentSectionPeak);
|
||||
}
|
||||
|
||||
private double reducedNoteCount => 5;
|
||||
@ -89,145 +75,4 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
Math.Max(Math.Pow(difficulty, 1.5) * 20, Math.Pow(difficulty, 2) * 17.0),
|
||||
Math.Max(Math.Pow(difficulty, 3) * 10.5, Math.Pow(difficulty, 4) * 6.00));
|
||||
}
|
||||
|
||||
public class ReadingHidden : Aim
|
||||
{
|
||||
public ReadingHidden(Mod[] mods)
|
||||
: base(mods, false)
|
||||
{
|
||||
}
|
||||
protected new double SkillMultiplier => 7.2;
|
||||
|
||||
protected override double StrainValueAt(DifficultyHitObject current)
|
||||
{
|
||||
CurrentStrain *= StrainDecay(current.DeltaTime);
|
||||
|
||||
// We're not using slider aim because we assuming that HD doesn't makes sliders harder (what is not true, but we will ignore this for now)
|
||||
double hiddenDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false);
|
||||
hiddenDifficulty *= ReadingHiddenEvaluator.EvaluateDifficultyOf(current);
|
||||
hiddenDifficulty *= SkillMultiplier;
|
||||
|
||||
CurrentStrain += hiddenDifficulty;
|
||||
|
||||
return CurrentStrain;
|
||||
}
|
||||
|
||||
public new static double DifficultyToPerformance(double difficulty) => Math.Max(
|
||||
Math.Max(difficulty * 16, Math.Pow(difficulty, 2) * 10), Math.Pow(difficulty, 3) * 4);
|
||||
}
|
||||
|
||||
public class ReadingHighAR : GraphSkill
|
||||
{
|
||||
|
||||
private const double component_multiplier = 0.135;
|
||||
private const double component_default_value_multiplier = 60;
|
||||
public ReadingHighAR(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
aimComponent = new HighARAimComponent(mods);
|
||||
speedComponent = new HighARSpeedComponent(mods);
|
||||
}
|
||||
|
||||
private HighARAimComponent aimComponent;
|
||||
private HighARSpeedComponent speedComponent;
|
||||
|
||||
private readonly List<double> difficulties = new List<double>();
|
||||
private int objectsCount = 0;
|
||||
|
||||
public override void Process(DifficultyHitObject current)
|
||||
{
|
||||
aimComponent.Process(current);
|
||||
speedComponent.Process(current);
|
||||
|
||||
if (current.BaseObject is not Spinner)
|
||||
objectsCount++;
|
||||
|
||||
double power = OsuDifficultyCalculator.SUM_POWER;
|
||||
double mergedDifficulty = Math.Pow(
|
||||
Math.Pow(aimComponent.CurrentSectionPeak, power) +
|
||||
Math.Pow(speedComponent.CurrentSectionPeak, power), 1.0 / power);
|
||||
|
||||
difficulties.Add(mergedDifficulty);
|
||||
|
||||
if (current.Index == 0)
|
||||
CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
|
||||
|
||||
while (current.StartTime > CurrentSectionEnd)
|
||||
{
|
||||
StrainPeaks.Add(CurrentSectionPeak);
|
||||
CurrentSectionPeak = 0;
|
||||
CurrentSectionEnd += SectionLength;
|
||||
}
|
||||
|
||||
CurrentSectionPeak = Math.Max(mergedDifficulty, CurrentSectionPeak);
|
||||
}
|
||||
public override double DifficultyValue()
|
||||
{
|
||||
// Simulating summing to get the most correct value possible
|
||||
double aimValue = Math.Sqrt(aimComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER;
|
||||
double speedValue = Math.Sqrt(speedComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER;
|
||||
|
||||
double aimPerformance = OsuStrainSkill.DifficultyToPerformance(aimValue);
|
||||
double speedPerformance = OsuStrainSkill.DifficultyToPerformance(speedValue);
|
||||
|
||||
double power = OsuDifficultyCalculator.SUM_POWER;
|
||||
double totalPerformance = Math.Pow(Math.Pow(aimPerformance, power) + Math.Pow(speedPerformance, power), 1.0 / power);
|
||||
|
||||
// Length bonus is in SR to not inflate Star Rating short AR11 maps
|
||||
double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount);
|
||||
totalPerformance *= Math.Pow(lengthBonus, 4); // make it bypass sqrt
|
||||
|
||||
double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance);
|
||||
double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0);
|
||||
|
||||
return 53.2 * Math.Sqrt(difficultyValue);
|
||||
}
|
||||
|
||||
public class HighARAimComponent : Aim
|
||||
{
|
||||
public HighARAimComponent(Mod[] mods)
|
||||
: base(mods, true)
|
||||
{
|
||||
}
|
||||
|
||||
protected new double SkillMultiplier => base.SkillMultiplier * component_multiplier;
|
||||
|
||||
protected override double StrainValueAt(DifficultyHitObject current)
|
||||
{
|
||||
CurrentStrain *= StrainDecay(current.DeltaTime);
|
||||
|
||||
double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true) * SkillMultiplier;
|
||||
aimDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current, true);
|
||||
|
||||
CurrentStrain += aimDifficulty;
|
||||
|
||||
return CurrentStrain + component_default_value_multiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current, true);
|
||||
}
|
||||
}
|
||||
|
||||
public class HighARSpeedComponent : Speed
|
||||
{
|
||||
public HighARSpeedComponent(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
}
|
||||
|
||||
protected new double SkillMultiplier => base.SkillMultiplier * component_multiplier;
|
||||
|
||||
protected override double StrainValueAt(DifficultyHitObject current)
|
||||
{
|
||||
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current;
|
||||
|
||||
CurrentStrain *= StrainDecay(currObj.StrainTime);
|
||||
|
||||
double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * SkillMultiplier;
|
||||
speedDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current);
|
||||
CurrentStrain += speedDifficulty;
|
||||
|
||||
CurrentRhythm = currObj.RhythmDifficulty;
|
||||
double totalStrain = CurrentStrain * CurrentRhythm;
|
||||
return totalStrain + component_default_value_multiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// </summary>
|
||||
public class Speed : OsuStrainSkill
|
||||
{
|
||||
protected double SkillMultiplier => 1375;
|
||||
protected override double StrainDecayBase => 0.3;
|
||||
private double skillMultiplier => 1375;
|
||||
private double strainDecayBase => 0.3;
|
||||
|
||||
protected double CurrentStrain;
|
||||
protected double CurrentRhythm;
|
||||
private double currentStrain;
|
||||
private double currentRhythm;
|
||||
|
||||
protected override int ReducedSectionCount => 5;
|
||||
protected override double DifficultyMultiplier => 1.04;
|
||||
@ -32,17 +32,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
}
|
||||
|
||||
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (CurrentStrain * CurrentRhythm) * StrainDecay(time - current.Previous(0).StartTime);
|
||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||
|
||||
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * strainDecay(time - current.Previous(0).StartTime);
|
||||
|
||||
protected override double StrainValueAt(DifficultyHitObject current)
|
||||
{
|
||||
OsuDifficultyHitObject currODHO = (OsuDifficultyHitObject)current;
|
||||
currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime);
|
||||
currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier;
|
||||
|
||||
CurrentStrain *= StrainDecay(currODHO.StrainTime);
|
||||
CurrentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * SkillMultiplier;
|
||||
currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
|
||||
|
||||
CurrentRhythm = currODHO.RhythmDifficulty;
|
||||
double totalStrain = CurrentStrain * CurrentRhythm;
|
||||
double totalStrain = currentStrain * currentRhythm;
|
||||
|
||||
objectStrains.Add(totalStrain);
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// A abstract skill with available per objet difficulty.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class should be considered a "processing" class and not persisted.
|
||||
/// </remarks>
|
||||
public abstract class GraphSkill : Skill
|
||||
{
|
||||
protected GraphSkill(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The length of each section.
|
||||
/// </summary>
|
||||
protected virtual int SectionLength => 400;
|
||||
|
||||
public double CurrentSectionPeak { get; protected set; } // We also keep track of the peak level in the current section.
|
||||
|
||||
protected double CurrentSectionEnd;
|
||||
|
||||
protected readonly List<double> StrainPeaks = new List<double>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a live enumerable of the difficulties
|
||||
/// </summary>
|
||||
public virtual IEnumerable<double> GetCurrentStrainPeaks() => StrainPeaks.Append(CurrentSectionPeak);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -12,13 +13,24 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
/// Used to processes strain values of <see cref="DifficultyHitObject"/>s, keep track of strain levels caused by the processed objects
|
||||
/// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
|
||||
/// </summary>
|
||||
public abstract class StrainSkill : GraphSkill
|
||||
public abstract class StrainSkill : Skill
|
||||
{
|
||||
/// <summary>
|
||||
/// The weight by which each strain value decays.
|
||||
/// </summary>
|
||||
protected virtual double DecayWeight => 0.9;
|
||||
|
||||
/// <summary>
|
||||
/// The length of each strain section.
|
||||
/// </summary>
|
||||
protected virtual int SectionLength => 400;
|
||||
|
||||
private double currentSectionPeak; // We also keep track of the peak strain level in the current section.
|
||||
|
||||
private double currentSectionEnd;
|
||||
|
||||
private readonly List<double> strainPeaks = new List<double>();
|
||||
|
||||
protected StrainSkill(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
@ -36,16 +48,16 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
{
|
||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
if (current.Index == 0)
|
||||
CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
|
||||
currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
|
||||
|
||||
while (current.StartTime > CurrentSectionEnd)
|
||||
while (current.StartTime > currentSectionEnd)
|
||||
{
|
||||
saveCurrentPeak();
|
||||
startNewSectionFrom(CurrentSectionEnd, current);
|
||||
CurrentSectionEnd += SectionLength;
|
||||
startNewSectionFrom(currentSectionEnd, current);
|
||||
currentSectionEnd += SectionLength;
|
||||
}
|
||||
|
||||
CurrentSectionPeak = Math.Max(StrainValueAt(current), CurrentSectionPeak);
|
||||
currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -53,7 +65,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
/// </summary>
|
||||
private void saveCurrentPeak()
|
||||
{
|
||||
StrainPeaks.Add(CurrentSectionPeak);
|
||||
strainPeaks.Add(currentSectionPeak);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -65,7 +77,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
{
|
||||
// The maximum strain of the new section is not zero by default
|
||||
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
|
||||
CurrentSectionPeak = CalculateInitialStrain(time, current);
|
||||
currentSectionPeak = CalculateInitialStrain(time, current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -76,6 +88,12 @@ namespace osu.Game.Rulesets.Difficulty.Skills
|
||||
/// <returns>The peak strain.</returns>
|
||||
protected abstract double CalculateInitialStrain(double time, DifficultyHitObject current);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a live enumerable of the peak strains for each <see cref="SectionLength"/> section of the beatmap,
|
||||
/// including the peak of the current section.
|
||||
/// </summary>
|
||||
public IEnumerable<double> GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the calculated difficulty value representing all <see cref="DifficultyHitObject"/>s that have been processed up to this point.
|
||||
/// </summary>
|
||||
|
Loading…
x
Reference in New Issue
Block a user