1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-14 05:47:20 +08:00

numerous simplifications

This commit is contained in:
Givikap120 2024-10-21 02:25:15 +03:00
parent a0b2653398
commit cba409d84a
4 changed files with 50 additions and 133 deletions

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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>