Skip to content

Commit

Permalink
made aim strain system use linear decay, removed speedDeviation, nerf…
Browse files Browse the repository at this point in the history
…ed simple rhythm, rebalanced aim
  • Loading branch information
Xexxar committed Feb 6, 2024
1 parent 9c04359 commit 184e948
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 33 deletions.
24 changes: 11 additions & 13 deletions osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,9 @@ current.BaseObject is Spinner ||

// Snap Stuff
// Reduce strain time by 25ms to account for stopping time.
double snapDifficulty = Math.Max(linearDifficulty * (2 * osuCurrObj.Radius / Math.Max(10, osuCurrObj.StrainTime - 40) + currMovement.Length / (osuCurrObj.StrainTime - 25)),
double snapDifficulty = Math.Max(linearDifficulty * (2 * osuCurrObj.Radius / Math.Max(10, Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime) - 50) + currMovement.Length / (osuCurrObj.StrainTime - 0)),
linearDifficulty * currMovement.Length / currTime);

double snapBuff = Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime) / (Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime) - 25);

// Begin angle and weird rewards.
double currVelocity = currMovement.Length / osuCurrObj.StrainTime;
double prevVelocity = prevMovement.Length / osuLastObj0.StrainTime;
Expand All @@ -85,24 +83,24 @@ current.BaseObject is Spinner ||
double lastAngle = osuLastObj0.Angle.Value;

Check failure on line 84 in osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Fix formatting
// We reward wide angles on snap.
snapAngle = snapBuff * linearDifficulty * calculateAngleSpline(Math.Abs(currAngle), false) * Math.Min(Math.Min(currVelocity, prevVelocity), (currMovement + prevMovement).Length / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime));
snapAngle = linearDifficulty * calculateAngleSpline(Math.Abs(currAngle), false) * Math.Min(Math.Min(currVelocity, prevVelocity), (currMovement + prevMovement).Length / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime));

// We reward for angle changes or the acuteness of the angle, whichever is higher. Possibly a case out there to reward both.
flowAngle = linearDifficulty * Math.Pow(Math.Sin((currAngle - lastAngle) / 2), 2) * Math.Min(currVelocity, prevVelocity)
+ linearDifficulty * calculateAngleSpline(Math.Abs(currAngle), true) * Math.Min(Math.Min(currVelocity, prevVelocity), (currMovement - prevMovement).Length / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime));
flowAngle = linearDifficulty * calculateAngleSpline(Math.Abs(currAngle), true) * Math.Min(Math.Min(currVelocity, prevVelocity), (currMovement - prevMovement).Length / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime));

flowAngle += Math.Pow(Math.Sin((currAngle - lastAngle) / 2), 2) * Math.Min(currVelocity * currMovement.Length / (osuCurrObj.Radius * 2), prevVelocity * prevMovement.Length / (osuCurrObj.Radius * 2));
}

double flowVelChange = linearDifficulty * Math.Max(0, Math.Min(Math.Abs(currVelocity - prevVelocity) - Math.Min(currVelocity, prevVelocity), Math.Max(osuCurrObj.Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime), Math.Min(currVelocity, prevVelocity))));
// double flowVelChange = linearDifficulty * Math.Max(0, Math.Min(Math.Abs(currVelocity - prevVelocity) - Math.Min(currVelocity, prevVelocity), Math.Max(osuCurrObj.Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime), Math.Min(currVelocity, prevVelocity))));

// double flowVelChange = linearDifficulty * Math.Abs(prevVelocity - currVelocity) * rhythmRatio;
double flowVelChange = linearDifficulty * Math.Abs(prevVelocity - currVelocity);
double snapVelChange = linearDifficulty * Math.Max(0, Math.Min(Math.Abs(prevVelocity - currVelocity) - Math.Min(currVelocity, prevVelocity), Math.Max(osuCurrObj.Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime), Math.Min(currVelocity, prevVelocity))));

snapDifficulty += snapBuff * snapVelChange + snapAngle;
flowDifficulty += flowVelChange + flowAngle;
snapDifficulty += Math.Max(snapVelChange, snapAngle);
flowDifficulty += Math.Max(flowVelChange, flowAngle);

// Apply balancing parameters.
flowDifficulty = flowDifficulty * 1.4125;
snapDifficulty = snapDifficulty * 0.79;
snapDifficulty *= 0.655;
flowDifficulty *= 0.85;

Check failure on line 104 in osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Fix formatting
// Apply small CS buff.
snapDifficulty *= Math.Sqrt(linearDifficulty);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
public static class RhythmEvaluator
{
private const int history_time_max = 5000; // 5 seconds of calculatingRhythmBonus max.
private const double rhythm_multiplier = 1.5;
private const double rhythm_multiplier = 1.0;

/// <summary>
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current <see cref="OsuDifficultyHitObject"/>.
Expand Down Expand Up @@ -50,7 +50,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current)
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
double currRatio = Math.PI * Math.Min(5, Math.Max(prevDelta, currDelta) / Math.Min(prevDelta, currDelta));
currRatio = 1.0 + 8 * (-0.25 + Math.Min(0.75, Math.Max(0.25, Math.Pow(Math.Sin(currRatio), 2.0) + 0.2 * Math.Pow(Math.Sin(1.5 * currRatio), 2.0))));
currRatio = 1.0 + 12 * (-0.25 + Math.Min(0.75, Math.Max(0.25, Math.Pow(Math.Sin(currRatio), 2.0) + 0.2 * Math.Pow(Math.Sin(1.5 * currRatio), 2.0))));

double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3));

Expand Down
25 changes: 22 additions & 3 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,24 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat

double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier;
double speedNotes = ((Speed)skills[2]).RelevantNoteCount();
double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier;
double aimFlowRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier;
double aimSnapRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[4].DifficultyValue()) * difficulty_multiplier;
double speedNotes = ((Speed)skills[4]).RelevantNoteCount();
double flashlightRating = Math.Sqrt(skills[5].DifficultyValue()) * difficulty_multiplier;

double p = 5.0;

if (aimSnapRating > aimFlowRating)
{
aimRating = Math.Pow(Math.Pow(aimRating, p) + Math.Pow(aimFlowRating, p), 1.0 / p);
aimRatingNoSliders = Math.Pow(Math.Pow(aimRatingNoSliders, p) + Math.Pow(aimFlowRating, p), 1.0 / p);
}
else
{
aimRating = Math.Pow(Math.Pow(aimRating, p) + Math.Pow(aimSnapRating, p), 1.0 / p);
aimRatingNoSliders = Math.Pow(Math.Pow(aimRatingNoSliders, p) + Math.Pow(aimSnapRating, p), 1.0 / p);
}

if (mods.Any(m => m is OsuModTouchDevice))
{
Expand All @@ -57,6 +72,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
flashlightRating *= 0.7;
}



double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
double baseAimNoSlidersPerformance = Math.Pow(5 * Math.Max(1, aimRatingNoSliders / 0.0675) - 4, 3) / 100000;
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
Expand Down Expand Up @@ -133,6 +150,8 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo
{
new Aim(mods, true),
new Aim(mods, false),
new AimFlow(mods),
new AimSnap(mods),
new Speed(mods),
new Flashlight(mods)
};
Expand Down
6 changes: 3 additions & 3 deletions osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut

double approachRateFactor = 0.0;
if (attributes.ApproachRate > 10.33)
approachRateFactor = 0.12 * (attributes.ApproachRate - 10.33);
approachRateFactor = 0.15 * (attributes.ApproachRate - 10.33);
else if (attributes.ApproachRate < 9.0)
approachRateFactor = 0.05 * (9.0 - attributes.ApproachRate);

Expand Down Expand Up @@ -154,7 +154,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib

double approachRateFactor = 0.0;
if (attributes.ApproachRate > 10.33)
approachRateFactor = 0.24 * (attributes.ApproachRate - 10.33);
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);

speedValue *= 1.0 + approachRateFactor;

Expand All @@ -175,7 +175,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib

// Scale the speed value with speed deviation.
if (deviation != null)
speedValue *= 1.0 / (1.0 + Math.Pow((double)speedDeviation / (12.0 + Math.Pow(hitWindow300, .75)), 4.0));
speedValue *= 1.0 / (1.0 + Math.Pow((double)deviation / (10.0 + Math.Pow(hitWindow300, .75)), 4.0));

return speedValue;
}
Expand Down
12 changes: 7 additions & 5 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public Aim(Mod[] mods, bool withSliders)
private double currentSnapStrain;
private double realStrain;

private double skillMultiplier => 32;//38.75;
private double skillMultiplier => 27.27;//38.75;
// private double skillMultiplier => 23.55;
private double strainDecayBase => 0.15;

Expand All @@ -39,8 +39,11 @@ public Aim(Mod[] mods, bool withSliders)

protected override double StrainValueAt(DifficultyHitObject current)
{
currentFlowStrain *= strainDecay(current.DeltaTime);
currentSnapStrain *= strainDecay(current.DeltaTime);
// currentFlowStrain *= strainDecay(current.DeltaTime);
// currentSnapStrain *= strainDecay(current.DeltaTime);

currentFlowStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000));
currentSnapStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000));

double currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);

Expand All @@ -54,8 +57,7 @@ protected override double StrainValueAt(DifficultyHitObject current)
else
currentSnapStrain += snapStrain;

double p = 3;
realStrain = currentFlowStrain + currentSnapStrain + (Math.Pow(Math.Pow(currentFlowStrain, p) + Math.Pow(currentSnapStrain, p), 1.0 / p) - Math.Max(currentFlowStrain, currentSnapStrain));
realStrain = currentFlowStrain + currentSnapStrain;

return realStrain;
}
Expand Down
62 changes: 62 additions & 0 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/AimFlow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
using System.Collections.Generic;

namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
/// <summary>
/// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
/// </summary>
public class AimFlow : OsuStrainSkill
{
public AimFlow(Mod[] mods)
: base(mods)
{
}

private double currentFlowStrain;
private double currentSnapStrain;
private double realStrain;

private double skillMultiplier => 27.27;//38.75;
// private double skillMultiplier => 23.55;
private double strainDecayBase => 0.15;

private readonly List<double> flowStrains = new List<double>();
private readonly List<double> snapStrains = new List<double>();

private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);

protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => realStrain * strainDecay(time - current.Previous(0).StartTime);

protected override double StrainValueAt(DifficultyHitObject current)
{
// currentFlowStrain *= strainDecay(current.DeltaTime);
// currentSnapStrain *= strainDecay(current.DeltaTime);

currentFlowStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000));
currentSnapStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000));

double currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);

(double, double) aimResult = AimEvaluator.EvaluateDifficultyOf(current, true, strainDecayBase, currentRhythm);

double flowStrain = aimResult.Item1 * skillMultiplier;
double snapStrain = aimResult.Item2 * skillMultiplier;

if (flowStrain < snapStrain)
currentFlowStrain += flowStrain;
else
currentSnapStrain += snapStrain;

realStrain = currentFlowStrain;

return realStrain;
}
}
}
62 changes: 62 additions & 0 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/AimSnap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
using System.Collections.Generic;

namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
/// <summary>
/// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
/// </summary>
public class AimSnap : OsuStrainSkill
{
public AimSnap(Mod[] mods)
: base(mods)
{
}

private double currentFlowStrain;
private double currentSnapStrain;
private double realStrain;

private double skillMultiplier => 27.27;//38.75;
// private double skillMultiplier => 23.55;
private double strainDecayBase => 0.15;

private readonly List<double> flowStrains = new List<double>();
private readonly List<double> snapStrains = new List<double>();

private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);

protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => realStrain * strainDecay(time - current.Previous(0).StartTime);

protected override double StrainValueAt(DifficultyHitObject current)
{
// currentFlowStrain *= strainDecay(current.DeltaTime);
// currentSnapStrain *= strainDecay(current.DeltaTime);

currentFlowStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000));
currentSnapStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000));

double currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);

(double, double) aimResult = AimEvaluator.EvaluateDifficultyOf(current, true, strainDecayBase, currentRhythm);

double flowStrain = aimResult.Item1 * skillMultiplier;
double snapStrain = aimResult.Item2 * skillMultiplier;

if (flowStrain < snapStrain)
currentFlowStrain += flowStrain;
else
currentSnapStrain += snapStrain;

realStrain = currentSnapStrain;

return realStrain;
}
}
}
47 changes: 41 additions & 6 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ public abstract class OsuStrainSkill : StrainSkill
protected OsuStrainSkill(Mod[] mods)
: base(mods)
{
strains = new List<double>();
}

private List<double> strains;

public override double DifficultyValue()
{
double difficulty = 0;
Expand All @@ -47,14 +50,14 @@ public override double DifficultyValue()
// These sections will not contribute to the difficulty.
var peaks = GetCurrentStrainPeaks().Where(p => p > 0);

List<double> strains = peaks.OrderByDescending(d => d).ToList();
strains = peaks.OrderByDescending(d => d).ToList();

// We are reducing the highest strains first to account for extreme difficulty spikes
for (int i = 0; i < Math.Min(strains.Count, ReducedSectionCount); i++)
{
double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp((float)i / ReducedSectionCount, 0, 1)));
strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale);
}
// for (int i = 0; i < Math.Min(strains.Count, ReducedSectionCount); i++)
// {
// double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp((float)i / ReducedSectionCount, 0, 1)));
// strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale);
// }

int index = 0;

Expand All @@ -75,5 +78,37 @@ public override double DifficultyValue()

return difficulty * DifficultyMultiplier;
}

// public double difficultyValueWithThreshold(double startIndex)
// {
// // threshold = 1.0 - threshold;
// // int startIndex = (int)(threshold * strains.Count);

// int index = 0;
// double difficulty = 0.0;

// // Difficulty is the weighted sum of the highest strains from every section.
// // We're sorting from highest to lowest strain.
// foreach (double strain in strains.OrderByDescending(d => d))
// {
// // Below uses harmonic sum scaling which makes the resulting summation logarithmic rather than geometric.
// // Good for properly weighting difficulty across full map instead of using object count for LengthBonus.
// // 1.44 and 7.5 are arbitrary constants that worked well.
// // double weight = 1.44 * ((1 + (7.5 / (1 + index))) / (index + 1 + (7.5 / (1 + index))));
// // double weight = 1.42 * ((1 + (7.5 / (1 + index))) / (Math.Pow(index, Math.Max(0.85, 1.0 - 0.15 * Math.Pow(index / 1500.0, 1))) + 1 + (7.5 / (1 + index))));
// if (startIndex > index)
// {
// index += 1;
// continue;
// }

// double weight = (1.0 + (20.0 / (1.0 + (index - startIndex)))) / (index + 1.0 + (20.0 / (1.0 + (index - startIndex))));

// difficulty += strain * weight;
// index += 1;
// }

// return difficulty;
// }
}
}
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// </summary>
public class Speed : OsuStrainSkill
{
private double skillMultiplier => 1050;
private double skillMultiplier => 1000;
private double strainDecayBase => 0.3;

private double currentStrain;
Expand Down

0 comments on commit 184e948

Please sign in to comment.