1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-12 18:52:57 +08:00

Added inpredictability calculation

Used mainly in high AR
also used in HD
This commit is contained in:
Givikap120 2024-01-26 22:32:23 +02:00
parent cede416d41
commit 394af044d9
3 changed files with 90 additions and 18 deletions

View File

@ -72,46 +72,114 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty;
// Console.WriteLine($"Object {currObj.StartTime}, {hiddenDifficulty:0.##} + {noteDensityDifficulty:0.##} + {overlapDifficulty:0.##}");
return difficulty;
}
public static double EvaluateHighARDifficultyOf(DifficultyHitObject current, bool applyFollowLineAdjust = false)
public static double EvaluateHighARDifficultyOf(DifficultyHitObject current, bool applyAdjust = false)
{
// follow lines make high AR easier, so apply nerf if object isn't new combo
// double adjustedApproachTime = osuCurrObj.Preempt;
// if (applyFollowLineAdjust) adjustedApproachTime += Math.Max(0, (osuCurrObj.FollowLineTime - 200) / 25);
var currObj = (OsuDifficultyHitObject)current;
return highArCurve(currObj.Preempt);
double result = highArCurve(currObj.Preempt);
if (applyAdjust)
{
double inpredictability = 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.85 + 0.75 * inpredictability;
}
return result;
}
public static double EvaluateHiddenDifficultyOf(DifficultyHitObject current)
{
var currObj = (OsuDifficultyHitObject)current;
// Maybe I should just pass in clockrate...
double clockRateEstimate = current.BaseObject.StartTime / currObj.StartTime;
double currVelocity = currObj.LazyJumpDistance / currObj.StrainTime;
double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false);
double hdDifficulty = 0;
double timeSpentInvisible = getDurationSpentInvisible(currObj) / clockRateEstimate;
double timeSpentInvisible = getDurationSpentInvisible(currObj) / currObj.ClockRate;
double timeDifficultyFactor = calculateDenstityOf(currObj) / 1000;
double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15);
hdDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible * timeDifficultyFactor, 1) +
(8 + visibleObjectFactor) * currVelocity;
(8 + visibleObjectFactor) * aimDifficulty;
hdDifficulty *= 0.95 + 0.15 * EvaluateInpredictabilityOf(current); // Max multiplier is 1.1
return hdDifficulty;
}
public static double EvaluatePredictabilityOf(DifficultyHitObject current)
// Returns value from 0 to 1, where 0 is very predictable and 1 is very unpredictable
public static double EvaluateInpredictabilityOf(DifficultyHitObject current)
{
return 0;
// make the sum equal to 1
const double velocity_change_part = 0.3;
const double angle_change_part = 0.6;
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);
double velocityChangeBonus = 0;
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime;
// 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
velocityChangeBonus = velocityChange / Math.Max(currVelocity, prevVelocity); // maxiumum is 0.4
velocityChangeBonus /= 0.4;
}
double angleChangeBonus = 0;
if (osuCurrObj.Angle != null && osuLastObj.Angle != null && currVelocity > 0 && prevVelocity > 0)
{
angleChangeBonus = Math.Pow(Math.Sin((double)((osuCurrObj.Angle - osuLastObj.Angle) / 2)), 2); // Also stealed from xexxar
angleChangeBonus *= Math.Min(currVelocity, prevVelocity) / Math.Max(currVelocity, prevVelocity); // Prevent cheesing
}
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 = 1 - Math.Min(currDelta, lastDelta) / Math.Max(currDelta, lastDelta);
}
double result = velocity_change_part * velocityChangeBonus + angle_change_part * angleChangeBonus + rhythm_change_part * rhythmChangeBonus;
return result;
}
// Returns a list of objects that are visible on screen at
@ -198,7 +266,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// https://www.desmos.com/calculator/hbj7swzlth
private static double highArCurve(double preempt)
{
double value = Math.Pow(4, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms
double value = Math.Pow(3, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms
value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms)
return value;
}

View File

@ -99,6 +99,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
private readonly OsuHitObject lastObject;
public readonly double Preempt;
public readonly double FollowLineTime;
public readonly double ClockRate;
public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject? lastLastObject, double clockRate, List<DifficultyHitObject> objects, int index)
: base(hitObject, lastObject, clockRate, objects, index)
@ -106,6 +107,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
this.lastLastObject = lastLastObject as OsuHitObject;
this.lastObject = (OsuHitObject)lastObject;
Preempt = BaseObject.TimePreempt / clockRate;
FollowLineTime = 800 / clockRate; // 800ms is follow line appear time
FollowLineTime *= ((OsuHitObject)hitObject).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);

View File

@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
currentStrain *= strainDecay(current.DeltaTime);
double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true, ((OsuDifficultyHitObject)current).Preempt);
aimDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current);
aimDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current, true);
currentStrain += aimDifficulty * skillMultiplier;
// currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
currentStrain *= strainDecay(currODHO.StrainTime);
double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier;
speedDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current);
speedDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current, false);
currentStrain += speedDifficulty;
currentRhythm = currODHO.RhythmDifficulty;