1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-15 12:42:56 +08:00

Alternating angle nerf

Fixed alternating angle (fiery jumps for example) gaining too much unfair reading pp
Reworked similar-angle nerf as a whole
Normalised global pp multiplier
This commit is contained in:
Givikap120 2024-01-28 23:51:56 +02:00
parent 7c294c864f
commit 51eb5c0a01
4 changed files with 108 additions and 32 deletions

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const double overlap_multiplier = 0.8; private const double overlap_multiplier = 0.8;
private static double calculateDenstityOf(OsuDifficultyHitObject currObj) public static double CalculateDenstityOf(OsuDifficultyHitObject currObj)
{ {
double pastObjectDifficultyInfluence = 0; double pastObjectDifficultyInfluence = 0;
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
return pastObjectDifficultyInfluence; return pastObjectDifficultyInfluence;
} }
private static double calculateOverlapDifficultyOf(OsuDifficultyHitObject currObj) public static double CalculateOverlapDifficultyOf(OsuDifficultyHitObject currObj)
{ {
double screenOverlapDifficulty = 0; double screenOverlapDifficulty = 0;
@ -62,15 +62,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
var currObj = (OsuDifficultyHitObject)current; var currObj = (OsuDifficultyHitObject)current;
double pastObjectDifficultyInfluence = calculateDenstityOf(currObj); double pastObjectDifficultyInfluence = CalculateDenstityOf(currObj);
double screenOverlapDifficulty = calculateOverlapDifficultyOf(currObj); double screenOverlapDifficulty = CalculateOverlapDifficultyOf(currObj);
double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence)), 2.3); double difficulty = Math.Pow(4 * Math.Log(Math.Max(1, pastObjectDifficultyInfluence)), 2.3);
screenOverlapDifficulty = Math.Max(0, screenOverlapDifficulty - 0.5); // make overlap value =1 cost significantly less screenOverlapDifficulty = Math.Max(0, screenOverlapDifficulty - 0.5); // make overlap value =1 cost significantly less
difficulty *= getConstantAngleNerfFactor(currObj);
difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty; double overlapBonus = overlap_multiplier * screenOverlapDifficulty * difficulty;
difficulty *= getConstantAngleNerfFactor(currObj);
difficulty += overlapBonus;
//difficulty *= 1 + overlap_multiplier * screenOverlapDifficulty;
return difficulty; return difficulty;
} }
@ -103,12 +107,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double hdDifficulty = 0; double hdDifficulty = 0;
double timeSpentInvisible = getDurationSpentInvisible(currObj) / currObj.ClockRate; double timeSpentInvisible = getDurationSpentInvisible(currObj) / currObj.ClockRate;
double timeDifficultyFactor = calculateDenstityOf(currObj) / 1000;
double density = 1 + Math.Max(0, CalculateDenstityOf(currObj) - 1);
density *= getConstantAngleNerfFactor(currObj);
double timeDifficultyFactor = density / 1000;
double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15); double visibleObjectFactor = Math.Clamp(retrieveCurrentVisibleObjects(currObj).Count - 2, 0, 15);
hdDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible * timeDifficultyFactor, 1) + hdDifficulty += Math.Pow(visibleObjectFactor * timeSpentInvisible * timeDifficultyFactor, 1) +
(8 + visibleObjectFactor) * aimDifficulty; (6 + visibleObjectFactor) * aimDifficulty;
hdDifficulty *= 0.95 + 0.15 * EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 hdDifficulty *= 0.95 + 0.15 * EvaluateInpredictabilityOf(current); // Max multiplier is 1.1
@ -175,13 +183,28 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
lastDelta = Math.Max(0, lastDelta); lastDelta = Math.Max(0, lastDelta);
} }
rhythmChangeBonus = 1 - Math.Min(currDelta, lastDelta) / Math.Max(currDelta, lastDelta); rhythmChangeBonus = getRhythmDifference(currDelta, lastDelta);
} }
double result = velocity_change_part * velocityChangeBonus + angle_change_part * angleChangeBonus + rhythm_change_part * rhythmChangeBonus; double result = velocity_change_part * velocityChangeBonus + angle_change_part * angleChangeBonus + rhythm_change_part * rhythmChangeBonus;
return result; return result;
} }
public static double EvaluateLowDensityBonusOf(DifficultyHitObject current)
{
//var currObj = (OsuDifficultyHitObject)current;
//// Density = 2 in general means 3 notes on screen (it's not including current note)
//double density = CalculateDenstityOf(currObj);
//// We are considering density = 1.5 as starting point, 1.0 is noticably uncomfy and 0.5 is severely uncomfy
//double bonus = 1.5 - density;
//if (bonus <= 0) return 0;
//return Math.Pow(bonus, 2);
return 0;
}
// Returns a list of objects that are visible on screen at // Returns a list of objects that are visible on screen at
// the point in time at which the current object becomes visible. // the point in time at which the current object becomes visible.
private static IEnumerable<OsuDifficultyHitObject> retrievePastVisibleObjects(OsuDifficultyHitObject current) private static IEnumerable<OsuDifficultyHitObject> retrievePastVisibleObjects(OsuDifficultyHitObject current)
@ -237,6 +260,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
int index = 0; int index = 0;
double currentTimeGap = 0; double currentTimeGap = 0;
OsuDifficultyHitObject prevLoopObj = current;
OsuDifficultyHitObject? prevLoopObj1 = null;
OsuDifficultyHitObject? prevLoopObj2 = null;
double prevConstantAngle = 0;
while (currentTimeGap < time_limit) while (currentTimeGap < time_limit)
{ {
var loopObj = (OsuDifficultyHitObject)current.Previous(index); var loopObj = (OsuDifficultyHitObject)current.Previous(index);
@ -246,18 +276,52 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - time_limit_low) / (time_limit - time_limit_low), 0, 1); double longIntervalFactor = Math.Clamp(1 - (loopObj.StrainTime - time_limit_low) / (time_limit - time_limit_low), 0, 1);
if (loopObj.Angle.IsNotNull() && current.Angle.IsNotNull()) if (loopObj.Angle.IsNotNull() && prevLoopObj.Angle.IsNotNull())
{ {
double angleDifference = Math.Abs(current.Angle.Value - loopObj.Angle.Value); double angleDifference = Math.Abs(prevLoopObj.Angle.Value - loopObj.Angle.Value);
constantAngleCount += Math.Cos(4 * Math.Min(Math.PI / 8, angleDifference)) * longIntervalFactor;
// Nerf alternating angles case
if (prevLoopObj1.IsNotNull() && prevLoopObj2.IsNotNull() && prevLoopObj1.Angle.IsNotNull() && prevLoopObj2.Angle.IsNotNull())
{
// Normalized difference
double angleDifference1 = Math.Abs(prevLoopObj1.Angle.Value - loopObj.Angle.Value) / Math.PI;
double angleDifference2 = Math.Abs(prevLoopObj2.Angle.Value - prevLoopObj.Angle.Value) / Math.PI;
// Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0
double alternatingFactor = Math.Pow((1 - angleDifference1) * (1 - angleDifference2), 2);
// Be sure to nerf only same rhythms
double rhythmFactor = 1 - getRhythmDifference(loopObj.StrainTime, prevLoopObj.StrainTime); // 0 on different rhythm, 1 on same rhythm
rhythmFactor *= 1 - getRhythmDifference(prevLoopObj.StrainTime, prevLoopObj1.StrainTime);
rhythmFactor *= 1 - getRhythmDifference(prevLoopObj1.StrainTime, prevLoopObj2.StrainTime);
double acuteAngleFactor = 1 - Math.Min(loopObj.Angle.Value, prevLoopObj.Angle.Value) / Math.PI;
double prevAngleAdjust = Math.Max(angleDifference - angleDifference1, 0);
prevAngleAdjust *= alternatingFactor; // Nerf if alternating
prevAngleAdjust *= rhythmFactor; // Nerf if same rhythms
prevAngleAdjust *= acuteAngleFactor;
angleDifference -= prevAngleAdjust;
}
double currConstantAngle = Math.Cos(4 * Math.Min(Math.PI / 8, angleDifference)) * longIntervalFactor;
constantAngleCount += Math.Min(currConstantAngle, prevConstantAngle);
prevConstantAngle = currConstantAngle;
} }
currentTimeGap = current.StartTime - loopObj.StartTime; currentTimeGap = current.StartTime - loopObj.StartTime;
index++; index++;
prevLoopObj2 = prevLoopObj1;
prevLoopObj1 = prevLoopObj;
prevLoopObj = loopObj;
} }
return Math.Pow(Math.Min(1, 2 / constantAngleCount), 2); return Math.Pow(Math.Min(1, 2 / constantAngleCount), 2);
} }
private static double getTimeNerfFactor(double deltaTime) private static double getTimeNerfFactor(double deltaTime)
{ {
return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1);
@ -270,6 +334,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms) value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms)
return value; return value;
} }
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 / (1 + Math.Exp(-x));
// We are using mutiply and divide instead of add and subtract, so values won't be negative // We are using mutiply and divide instead of add and subtract, so values won't be negative

View File

@ -22,10 +22,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
public class OsuDifficultyCalculator : DifficultyCalculator public class OsuDifficultyCalculator : DifficultyCalculator
{ {
private const double difficulty_multiplier = 0.0675; private const double difficulty_multiplier = 0.067;
public override int Version => 20220902; public override int Version => 20220902;
public static double SumPower => 1.1; public static double SumPower => 1.1;
public static double FLSumPower => 1.6;
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
@ -75,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double baseReadingHighARPerformance = Math.Pow(5 * Math.Max(1, readingHighARRating / 0.0675) - 4, 3) / 100000; double baseReadingHighARPerformance = Math.Pow(5 * Math.Max(1, readingHighARRating / 0.0675) - 4, 3) / 100000;
double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SumPower) + Math.Pow(baseReadingHighARPerformance, SumPower), 1.0 / SumPower); double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SumPower) + Math.Pow(baseReadingHighARPerformance, SumPower), 1.0 / SumPower);
double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, 1.8) + Math.Pow(baseReadingARPerformance, 1.8), 1.0 / 1.8); double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FLSumPower) + Math.Pow(baseReadingARPerformance, FLSumPower), 1.0 / FLSumPower);
double baseReadingHiddenPerformance = 0; double baseReadingHiddenPerformance = 0;
if (mods.Any(h => h is OsuModHidden)) if (mods.Any(h => h is OsuModHidden))

View File

@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
public static double SumPower => 1.1; // Maybe it should just use OsuDifficultyCalculator SumPower
private double accuracy; private double accuracy;
private int scoreMaxCombo; private int scoreMaxCombo;
private int countGreat; private int countGreat;
@ -64,9 +62,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits); effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits);
} }
double power = OsuDifficultyCalculator.SumPower;
double aimValue = computeAimValue(score, osuAttributes); double aimValue = computeAimValue(score, osuAttributes);
double speedValue = computeSpeedValue(score, osuAttributes); double speedValue = computeSpeedValue(score, osuAttributes);
double mechanicalValue = Math.Pow(Math.Pow(aimValue, SumPower) + Math.Pow(speedValue, SumPower), 1.0 / SumPower); double mechanicalValue = Math.Pow(Math.Pow(aimValue, power) + Math.Pow(speedValue, power), 1.0 / power);
// Cognition // Cognition
@ -77,19 +77,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty
flashlightValue = 0.0; flashlightValue = 0.0;
double readingARValue = computeReadingARValue(score, osuAttributes); double readingARValue = computeReadingARValue(score, osuAttributes);
// Reduce AR reading bonus if FL is present // Reduce AR reading bonus if FL is present
double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, 1.8) + Math.Pow(readingARValue, 1.8), 1.0 / 1.8); double flPower = OsuDifficultyCalculator.FLSumPower;
double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, flPower) + Math.Pow(readingARValue, flPower), 1.0 / flPower);
double readingNonARValue = computeReadingNonARValue(score, osuAttributes); double readingNonARValue = computeReadingNonARValue(score, osuAttributes);
double cognitionValue = Math.Pow(Math.Pow(flashlightARValue, SumPower) + Math.Pow(readingNonARValue, SumPower), 1.0 / SumPower); double cognitionValue = Math.Pow(Math.Pow(flashlightARValue, power) + Math.Pow(readingNonARValue, power), 1.0 / power);
cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialFlashlightValue); cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialFlashlightValue);
double accuracyValue = computeAccuracyValue(score, osuAttributes); double accuracyValue = computeAccuracyValue(score, osuAttributes);
double totalValue = double totalValue =
Math.Pow( Math.Pow(
Math.Pow(mechanicalValue, SumPower) + Math.Pow(mechanicalValue, power) +
Math.Pow(cognitionValue, SumPower) + Math.Pow(cognitionValue, power) +
Math.Pow(accuracyValue, SumPower), 1.0 / SumPower Math.Pow(accuracyValue, power), 1.0 / power
) * multiplier; ) * multiplier;
return new OsuPerformanceAttributes return new OsuPerformanceAttributes
@ -247,9 +248,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeReadingARValue(ScoreInfo score, OsuDifficultyAttributes attributes) private double computeReadingARValue(ScoreInfo score, OsuDifficultyAttributes attributes)
{ {
//double readingARValue = Math.Max(computeReadingLowARValue(score, attributes), computeReadingHighARValue(score, attributes)); //double readingARValue = Math.Max(computeReadingLowARValue(score, attributes), computeReadingHighARValue(score, attributes));
double power = OsuDifficultyCalculator.SumPower;
double readingValue = Math.Pow( double readingValue = Math.Pow(
Math.Pow(computeReadingLowARValue(score, attributes), SumPower) + Math.Pow(computeReadingLowARValue(score, attributes), power) +
Math.Pow(computeReadingHighARValue(score, attributes), SumPower), 1.0 / SumPower); Math.Pow(computeReadingHighARValue(score, attributes), power), 1.0 / power);
return readingValue; return readingValue;
} }

View File

@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
public class ReadingLowAR : GraphSkill public class ReadingLowAR : GraphSkill
{ {
private readonly List<double> difficulties = new List<double>(); private readonly List<double> difficulties = new List<double>();
//private double skillMultiplier => 5.5; //private double skillMultiplier => 2.3;
private double skillMultiplier => 2.3; private double skillMultiplier => 2;
public ReadingLowAR(Mod[] mods) public ReadingLowAR(Mod[] mods)
: base(mods) : base(mods)
@ -132,7 +132,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private double currentStrain; private double currentStrain;
// private double currentRhythm; // private double currentRhythm;
private double skillMultiplier => 13; //private double skillMultiplier => 13;
private double skillMultiplier => 14;
private double strainDecayBase => 0.15; private double strainDecayBase => 0.15;
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
@ -145,18 +146,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true, ((OsuDifficultyHitObject)current).Preempt); double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true, ((OsuDifficultyHitObject)current).Preempt);
aimDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current, true); aimDifficulty *= ReadingEvaluator.EvaluateHighARDifficultyOf(current, true);
currentStrain += aimDifficulty * skillMultiplier; aimDifficulty *= skillMultiplier;
// currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
double totalStrain = currentStrain; double totalStrain = currentStrain;
currentStrain += aimDifficulty;
// Warning: this line is unstable, so increasing amount of objects can decrease pp
totalStrain += aimDifficulty * (1 + ReadingEvaluator.EvaluateLowDensityBonusOf(current));
//Console.WriteLine($"{current.StartTime} - {ReadingEvaluator.EvaluateLowDensityBonusOf(current)}");
return totalStrain; return totalStrain;
} }
} }
public class HighARSpeedComponent : OsuStrainSkill public class HighARSpeedComponent : OsuStrainSkill
{ {
private double skillMultiplier => 650; private double skillMultiplier => 675;
private double strainDecayBase => 0.3; private double strainDecayBase => 0.3;
private double currentStrain; private double currentStrain;
@ -197,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
} }
private readonly List<double> difficulties = new List<double>(); private readonly List<double> difficulties = new List<double>();
private double skillMultiplier => 2.1; private double skillMultiplier => 2.3;
public override void Process(DifficultyHitObject current) public override void Process(DifficultyHitObject current)
{ {