mirror of
https://github.com/ppy/osu.git
synced 2025-03-14 05:47:20 +08:00
numerous simplifications
This commit is contained in:
parent
a0b2653398
commit
cba409d84a
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
@ -18,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
|
||||
private const double overlap_multiplier = 1;
|
||||
|
||||
private const double slider_body_length_multiplier = 1.3;
|
||||
|
||||
public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDistanceNerf = true, bool applySliderbodyDensity = true, double angleNerfMultiplier = 1.0)
|
||||
{
|
||||
var currObj = (OsuDifficultyHitObject)current;
|
||||
@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false);
|
||||
|
||||
// Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly.
|
||||
if (applyDistanceNerf) loopDifficulty *= (logistic((loopObj.MinimumJumpDistance - 80) / 10) + 0.2) / 1.2;
|
||||
if (applyDistanceNerf) loopDifficulty *= (logistic((loopObj.LazyJumpDistance - 80) / 10) + 0.2) / 1.2;
|
||||
|
||||
// Additional buff for long sliderbodies. OVERBUFFED ON PURPOSE
|
||||
if (applySliderbodyDensity && loopObj.BaseObject is Slider slider)
|
||||
@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
if (i > 0) maxBuff += 1;
|
||||
if (i < readingObjects.Count - 1) maxBuff += 1;
|
||||
|
||||
loopDifficulty *= 1 + 1.5 * Math.Min(sliderBodyBuff, maxBuff);
|
||||
loopDifficulty *= 1 + slider_body_length_multiplier * Math.Min(sliderBodyBuff, maxBuff);
|
||||
}
|
||||
|
||||
// Reduce density bonus for this object if they're too apart in time
|
||||
@ -85,6 +86,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
density += loopDifficulty;
|
||||
|
||||
// Angles nerf
|
||||
// Why it's /2 + 0.5?
|
||||
// Because there was a bug initially that made angle predictability to be from 0.5 to 1
|
||||
// And removing this bug caused balance to be destroyed
|
||||
double angleNerf = (loopObj.AnglePredictability / 2) + 0.5;
|
||||
|
||||
densityAnglesNerf += angleNerf * loopDifficulty * angleNerfMultiplier;
|
||||
@ -189,71 +193,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
// 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;
|
||||
|
||||
@ -271,12 +216,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
velocityChangeFactor /= 0.4;
|
||||
}
|
||||
|
||||
return velocityChangeFactor;
|
||||
// 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);
|
||||
|
||||
return velocityChangeFactor * rhythmSimilarity;
|
||||
}
|
||||
|
||||
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));
|
||||
private static double logistic(double x) => 1.0 / (1 + Math.Exp(-x));
|
||||
|
||||
// Finds the overlapness of the last object for which StartTime lower than target
|
||||
private static double boundBinarySearch(List<OsuDifficultyHitObject.ReadingObject> arr, double target)
|
||||
@ -315,59 +268,44 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
|
||||
double densityFactor = Math.Pow(density / 6.2, 1.5);
|
||||
|
||||
double invisibilityFactor;
|
||||
// High AR curve
|
||||
double invisibilityFactor = Math.Pow(preempt * 2.4 - 0.2, 5);
|
||||
|
||||
// AR11+DT and faster = 0 HD pp unless density is big
|
||||
if (preempt < 0.2) invisibilityFactor = 0;
|
||||
// Mid and Low AR curves
|
||||
invisibilityFactor = Math.Min(invisibilityFactor, Math.Max(preempt, preempt * 3 - 2.4));
|
||||
|
||||
// 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));
|
||||
// On 250ms invisibility factor will be 0
|
||||
invisibilityFactor *= invLerp(preempt, 0.25, 0.3);
|
||||
|
||||
|
||||
double hdDifficulty = invisibilityFactor + densityFactor;
|
||||
|
||||
// Scale by inpredictability slightly
|
||||
hdDifficulty *= 0.96 + 0.1 * ReadingEvaluator.EvaluateInpredictabilityOf(current); // Max multiplier is 1.1
|
||||
|
||||
return hdDifficulty;
|
||||
return invisibilityFactor + densityFactor;
|
||||
}
|
||||
|
||||
private static double invLerp(double value, double min, double max) => Math.Clamp((value - min) / (max - min), 0, 1);
|
||||
}
|
||||
|
||||
public static class ReadingHighAREvaluator
|
||||
{
|
||||
// https://www.desmos.com/calculator/mtlyw84ncw
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, bool applyAdjust = false)
|
||||
{
|
||||
var currObj = (OsuDifficultyHitObject)current;
|
||||
|
||||
double result = GetDifficulty(currObj.Preempt);
|
||||
// Get preempt in seconds
|
||||
double preempt = currObj.Preempt / 1000;
|
||||
|
||||
// This function is matching live high AR bonus
|
||||
double value = 0.63 * Math.Pow(Math.Max(8 - 20 * preempt, 0), 2.0 / 3);
|
||||
|
||||
// Use different curve for preempt < 375ms
|
||||
value = Math.Max(value, Math.Exp(9.075 - 80.0 * Math.Max(preempt, 0.375) / 3));
|
||||
|
||||
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;
|
||||
value *= 0.98 + 0.6 * inpredictability;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// High AR curve (this curve is without Math.Pow(value, 2))
|
||||
// https://www.desmos.com/calculator/xuuwd77cbq
|
||||
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);
|
||||
|
||||
return Math.Pow(value, 1.0 / ReadingHighAR.MECHANICAL_PP_POWER);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -342,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
var visibleObjects = new List<OsuDifficultyHitObject>();
|
||||
|
||||
for (int i = 0; i < current.Count; i++)
|
||||
for (int i = 0; i < current.Index; i++)
|
||||
{
|
||||
OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i);
|
||||
|
||||
@ -421,21 +421,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
double similarity3_2 = getGeneralSimilarity(prevObj0, prevObj3);
|
||||
double similarity3_3 = getGeneralSimilarity(prevObj1, prevObj4);
|
||||
|
||||
double similarity3_max = Math.Max(Math.Max(similarity3_1, similarity3_2), similarity3_3);
|
||||
double similarity3_total = similarity3_1 * similarity3_2 * similarity3_3;
|
||||
|
||||
double similarity3 = Math.Max(Math.Pow(similarity3_max, 3) * 0.5, similarity3_total);
|
||||
|
||||
// 4-4 repeat
|
||||
double similarity4_1 = getGeneralSimilarity(this, prevObj3);
|
||||
double similarity4_2 = getGeneralSimilarity(prevObj0, prevObj4);
|
||||
double similarity4_3 = getGeneralSimilarity(prevObj1, prevObj5);
|
||||
|
||||
double similarity4_max = Math.Pow(Math.Max(Math.Max(similarity4_1, similarity4_2), similarity4_3), 2);
|
||||
double similarity4_total = similarity4_1 * similarity4_2 * similarity4_3;
|
||||
|
||||
double similarity4 = Math.Max(Math.Pow(similarity4_max, 3) * 0.5, similarity4_total);
|
||||
|
||||
// Bandaid to fix Rubik's Cube +EZ
|
||||
double wideness = 0;
|
||||
if (Angle!.Value > Math.PI * 0.5)
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
public class ReadingLowAR : GraphSkill
|
||||
{
|
||||
private double skillMultiplier => 1.23;
|
||||
private double skillMultiplier => 1.22;
|
||||
private double aimComponentMultiplier => 0.4;
|
||||
|
||||
public ReadingLowAR(Mod[] mods)
|
||||
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
: base(mods, false)
|
||||
{
|
||||
}
|
||||
protected new double SkillMultiplier => 7.632;
|
||||
protected new double SkillMultiplier => 7.3;
|
||||
|
||||
protected override double StrainValueAt(DifficultyHitObject current)
|
||||
{
|
||||
@ -131,7 +131,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
private HighARSpeedComponent speedComponent;
|
||||
|
||||
private int objectsCount = 0;
|
||||
private double objectsPreemptSum = 0;
|
||||
|
||||
public override void Process(DifficultyHitObject current)
|
||||
{
|
||||
@ -141,7 +140,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
if (current.BaseObject is not Spinner)
|
||||
{
|
||||
objectsCount++;
|
||||
objectsPreemptSum += ((OsuDifficultyHitObject)current).Preempt;
|
||||
}
|
||||
|
||||
double power = OsuDifficultyCalculator.SUM_POWER;
|
||||
@ -164,8 +162,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
}
|
||||
|
||||
// Coefs for curve similar to difficulty to performance curve
|
||||
private static double power => 3.0369;
|
||||
private static double multiplier => 3.69656;
|
||||
private static double power => 3;
|
||||
private static double multiplier => 3.7;
|
||||
|
||||
public static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, power) * multiplier;
|
||||
private static double performanceToDifficulty(double performance) => Math.Pow(performance / multiplier, 1.0 / power);
|
||||
@ -185,18 +183,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
// Length bonus is in SR to not inflate Star Rating of short AR11 maps
|
||||
double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount);
|
||||
lengthBonus = Math.Pow(lengthBonus, 0.5 / MECHANICAL_PP_POWER);
|
||||
|
||||
// Get average preempt of objects
|
||||
double averagePreempt = objectsPreemptSum / objectsCount / 1000;
|
||||
|
||||
// Increase length bonus for long maps with very high AR
|
||||
// https://www.desmos.com/calculator/wz9wckqgzu
|
||||
double lengthBonusPower = 1 + 0.75 * Math.Pow(0.1, Math.Pow(2.3 * averagePreempt, 8));
|
||||
|
||||
// Be sure that increasing AR won't decrease pp
|
||||
if (lengthBonus < 1) lengthBonusPower = 2;
|
||||
|
||||
totalPerformance *= Math.Pow(lengthBonus, lengthBonusPower);
|
||||
totalPerformance *= lengthBonus;
|
||||
|
||||
double adjustedDifficulty = performanceToDifficulty(totalPerformance);
|
||||
double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0);
|
||||
@ -217,12 +204,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
CurrentStrain *= StrainDecay(current.DeltaTime);
|
||||
|
||||
double highARDifficulty = Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, true), 1.0 / MECHANICAL_PP_POWER);
|
||||
double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true) * SkillMultiplier;
|
||||
aimDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current, true);
|
||||
|
||||
aimDifficulty *= highARDifficulty;
|
||||
CurrentStrain += aimDifficulty;
|
||||
|
||||
return CurrentStrain + component_default_value_multiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current, true);
|
||||
return CurrentStrain + component_default_value_multiplier * highARDifficulty;
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,13 +227,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
|
||||
CurrentStrain *= StrainDecay(currObj.StrainTime);
|
||||
|
||||
double highARDifficulty = Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, false), 1.0 / MECHANICAL_PP_POWER);
|
||||
double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * SkillMultiplier;
|
||||
speedDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current);
|
||||
|
||||
speedDifficulty *= highARDifficulty;
|
||||
CurrentStrain += speedDifficulty;
|
||||
|
||||
CurrentRhythm = currObj.RhythmDifficulty;
|
||||
double totalStrain = CurrentStrain * CurrentRhythm;
|
||||
return totalStrain + component_default_value_multiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current);
|
||||
return totalStrain + component_default_value_multiplier * highARDifficulty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,6 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing
|
||||
{
|
||||
private readonly IReadOnlyList<DifficultyHitObject> difficultyHitObjects;
|
||||
|
||||
/// <summary>
|
||||
/// The index of this <see cref="DifficultyHitObject"/> in the list of all <see cref="DifficultyHitObject"/>s.
|
||||
/// </summary>
|
||||
public int Count => difficultyHitObjects.Count;
|
||||
|
||||
/// <summary>
|
||||
/// The index of this <see cref="DifficultyHitObject"/> in the list of all <see cref="DifficultyHitObject"/>s.
|
||||
/// </summary>
|
||||
|
Loading…
x
Reference in New Issue
Block a user