Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

January 2025 Diffcalc/PP release #31595

Open
wants to merge 70 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
79a3afe
Implement considerations for Relax within osu!taiko diffcalc (#30591)
Lawtrohux Dec 18, 2024
0f2f25d
Adjust `DifficultyValue` curve to avoid lower star rating of osu!taik…
YaniFR Dec 18, 2024
4ca88ae
Refactor `TaikoDifficultyCalculator` and add `DifficultStrain` attrib…
Lawtrohux Dec 19, 2024
ecd6b41
Increase `accscalingshift` and include `countok` in hit proportion (#…
Lawtrohux Dec 19, 2024
d8c3d89
remove particular condition on convert nerf (#31196)
Lawtrohux Dec 19, 2024
f722f94
Simplify osu! high-bpm acute angle jumps bonus (#30902)
stanriders Dec 20, 2024
f6a36f7
Implement `Reading` Skill into osu!taiko (#31208)
Lawtrohux Dec 21, 2024
6808a5a
Change slider drop penalty to use actual number of difficult sliders,…
stanriders Dec 21, 2024
3ddeaf8
Use `lastAngle` when nerfing repeated angles on acute bonus (#31245)
tsunyoku Dec 24, 2024
824497d
Rewrite of the `Rhythm` Skill within osu!taiko (#31284)
Lawtrohux Dec 27, 2024
988ed37
Add basic difficulty & performance calculation for Autopilot mod on o…
tsunyoku Dec 29, 2024
76ac11f
Fix angle bonuses calculating repetition incorrectly, apply distance …
stanriders Jan 6, 2025
4095b26
Add `consistentRatioPenalty` to the `Colour` skill. (#31285)
Lawtrohux Jan 7, 2025
3b58d5e
Clamp OD in performance calculation to fix negative OD gaining pp (#3…
stanriders Jan 7, 2025
392bb57
Simplify angle bonus formula (#31449)
stanriders Jan 8, 2025
db58ec8
Apply a bunch of balancing changes to aim (#31456)
stanriders Jan 9, 2025
b21c645
Punish speed PP for scores with high deviation (#30907)
Givikap120 Jan 9, 2025
c53188c
Use total deviation to scale accuracy on aim, general aim buff (#31498)
stanriders Jan 14, 2025
6cf15e3
Remove problematic total deviation scaling, rebalance aim (#31515)
tsunyoku Jan 14, 2025
5bed7c2
Remove lower cap on deviation without misses (#31499)
Natelytle Jan 14, 2025
0a21183
reading mono nerf (#31510)
Lawtrohux Jan 15, 2025
974fa76
fix spinners not increasing cumulative strain time (#31525)
molneya Jan 16, 2025
9da8dcd
osu!taiko stamina balancing (#31337)
Lawtrohux Jan 16, 2025
b9894f6
Bump NVika tool to 4.0.0
bdach Jan 16, 2025
a83f917
osu!taiko star rating and performance points rebalance (#31338)
Lawtrohux Jan 16, 2025
a42c03c
osu!taiko further considerations for rhythm (#31339)
Lawtrohux Jan 17, 2025
5b4ba92
Move error function from osu.Game.Utils to osu.Game.Rulesets.Difficul…
Natelytle Jan 17, 2025
8354cd5
Penalise the reading difficulty of high velocity notes using "note de…
buyaspacecube Jan 18, 2025
67723b3
Fix osu!catch "buzz slider" SR abuse (#31126)
bastoo0 Jan 18, 2025
e320f17
Remove redundant angle check (#31566)
tsunyoku Jan 19, 2025
e04727a
Improve convert considerations in osu!taiko (#31546)
Lawtrohux Jan 19, 2025
2d0bc6c
Rebalance stamina length bonus in osu!taiko (#31556)
Lawtrohux Jan 19, 2025
e575654
osu!taiko new rhythm penalty for long intervals using stamina difficu…
buyaspacecube Jan 20, 2025
22e839d
Replace indexed skill access with `skills.OfType<...>().Single()` (#3…
stanriders Jan 20, 2025
a77dfb1
Use correct `HitWindows` class for osu!taiko hit windows in difficult…
tsunyoku Jan 20, 2025
aeca37c
Merge branch 'master' into pp-dev
peppy Jan 21, 2025
c8b05ce
Tidy up code quality of `RhythmEvaluator`
peppy Jan 21, 2025
fa20bc6
Remove `EffectiveBPMPreprocessor`
tsunyoku Jan 21, 2025
dbe3688
Refactor `ColourEvaluator`
tsunyoku Jan 21, 2025
9919179
Format `ReadingEvaluator`
tsunyoku Jan 21, 2025
b8c79d5
Refactor `StaminaEvaluator`
tsunyoku Jan 21, 2025
ef88677
Add xmldoc to explain `IHasInterval.Interval`
tsunyoku Jan 21, 2025
20a76d8
Rename rhythm preprocessing objects to be clearer with intent
tsunyoku Jan 21, 2025
e0882d2
Make `rescale` a static method
tsunyoku Jan 21, 2025
764b000
Fix typo in `ColourEvaluator`
tsunyoku Jan 21, 2025
1c4bc6d
Revert `Precision.DefinitelyBigger` usage
tsunyoku Jan 21, 2025
14c68bc
Replace weird `IntervalGroupedHitObjects` inheritance layer
tsunyoku Jan 21, 2025
2c0d6b1
Fix incorrect namespace
tsunyoku Jan 22, 2025
753e9ef
Keep old behaviour of `double.PositiveInfinity` being the default for…
tsunyoku Jan 22, 2025
8f17a44
Remove unused default value
tsunyoku Jan 23, 2025
a7aa553
Fix incorrect `startTime` calculation
tsunyoku Jan 26, 2025
13c956c
Account for floating point errors
tsunyoku Jan 26, 2025
71b89c3
Rename class, rename children to hit objects and groups, make fields …
tsunyoku Jan 27, 2025
f3c17f1
Use correct English
tsunyoku Jan 27, 2025
4614496
Remove unnecessary strain sorting in difficult slider count (#31724)
Rian8337 Jan 29, 2025
2ee480c
Clamp `estimateImproperlyFollowedDifficultSliders` between 0 and `att…
tsunyoku Jan 30, 2025
fa844b0
Rename `Colour` / `Rhythm` related fields and classes
peppy Feb 5, 2025
709ad02
Simplify `TaikoRhythmData`'s ratio computation
peppy Feb 5, 2025
fc93390
Remove unused `HitObjectInterval`
peppy Feb 5, 2025
3254831
Tidy up xmldoc and remove another unused field
peppy Feb 5, 2025
8447679
Initial tidy-up pass on `IntervalGroupingUtils`
peppy Feb 5, 2025
40ea7ff
Add better documentation for interval change code
peppy Feb 5, 2025
2593946
Merge pull request #31636 from tsunyoku/taiko-cleanup
peppy Feb 6, 2025
4f6fd68
Fix inspections
peppy Feb 7, 2025
6c069b3
Merge branch 'master' into pp-dev
peppy Feb 7, 2025
9f90ebb
Calculate hit windows in performance calculator instead of databased …
tsunyoku Feb 7, 2025
b07d7e8
Merge branch 'master' into pp-dev
peppy Feb 7, 2025
d4ce712
Add note about weird taiko iteration
peppy Feb 10, 2025
340e081
Rename buzz variable per review
peppy Feb 10, 2025
afdcf40
Merge branch 'master' into pp-dev
peppy Feb 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,27 @@
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;

namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyAttributes : DifficultyAttributes
{
/// <summary>
/// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
/// <remarks>
/// Rate-adjusting mods don't directly affect the approach rate difficulty value, but have a perceived effect as a result of adjusting audio timing.
/// </remarks>
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }

public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;

// Todo: osu!catch should not output star rating in the 'aim' attribute.
yield return (ATTRIB_ID_AIM, StarRating);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
{
base.FromDatabaseAttributes(values, onlineInfo);

StarRating = values[ATTRIB_ID_AIM];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
Expand Down Expand Up @@ -35,14 +36,10 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
if (beatmap.HitObjects.Count == 0)
return new CatchDifficultyAttributes { Mods = mods };

// this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;

CatchDifficultyAttributes attributes = new CatchDifficultyAttributes
{
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier,
StarRating = Math.Sqrt(skills.OfType<Movement>().Single().DifficultyValue()) * difficulty_multiplier,
Mods = mods,
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
MaxCombo = beatmap.GetMaxCombo(),
};

Expand Down
17 changes: 16 additions & 1 deletion osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

using System;
using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
Expand Down Expand Up @@ -50,7 +53,19 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
if (catchAttributes.MaxCombo > 0)
value *= Math.Min(Math.Pow(score.MaxCombo, 0.8) / Math.Pow(catchAttributes.MaxCombo, 0.8), 1.0);

double approachRate = catchAttributes.ApproachRate;
var difficulty = score.BeatmapInfo!.Difficulty.Clone();

score.Mods.OfType<IApplicableToDifficulty>().ForEach(m => m.ApplyToDifficulty(difficulty));

var track = new TrackVirtual(10000);
score.Mods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
double clockRate = track.Rate;

// this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450) / clockRate;

double approachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0;

double approachRateFactor = 1.0;
if (approachRate > 9.0)
approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9
Expand Down
25 changes: 24 additions & 1 deletion osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public class Movement : StrainDecaySkill

private float? lastPlayerPosition;
private float lastDistanceMoved;
private float lastExactDistanceMoved;
private double lastStrainTime;
private bool isInBuzzSection;

/// <summary>
/// The speed multiplier applied to the player's catcher.
Expand Down Expand Up @@ -59,6 +61,9 @@ protected override double StrainValueOf(DifficultyHitObject current)

float distanceMoved = playerPosition - lastPlayerPosition.Value;

// For the exact position we consider that the catcher is in the correct position for both objects
float exactDistanceMoved = catchCurrent.NormalizedPosition - lastPlayerPosition.Value;

double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catcherSpeedMultiplier);

double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510);
Expand Down Expand Up @@ -92,12 +97,30 @@ protected override double StrainValueOf(DifficultyHitObject current)
playerPosition = catchCurrent.NormalizedPosition;
}

distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catcherSpeedMultiplier, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20)
* Math.Pow((Math.Min(catchCurrent.StrainTime * catcherSpeedMultiplier, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
}

// There is an edge case where horizontal back and forth sliders create "buzz" patterns which are repeated "movements" with a distance lower than
// the platter's width but high enough to be considered a movement due to the absolute_player_positioning_error and normalized_hitobject_radius offsets
// We are detecting this exact scenario. The first back and forth is counted but all subsequent ones are nullified.
// To achieve that, we need to store the exact distances (distance ignoring absolute_player_positioning_error and normalized_hitobject_radius)
if (Math.Abs(exactDistanceMoved) <= HalfCatcherWidth * 2 && exactDistanceMoved == -lastExactDistanceMoved && catchCurrent.StrainTime == lastStrainTime)
{
if (isInBuzzSection)
distanceAddition = 0;
else
isInBuzzSection = true;
}
else
{
isInBuzzSection = false;
}

lastPlayerPosition = playerPosition;
lastDistanceMoved = distanceMoved;
lastStrainTime = catchCurrent.StrainTime;
lastExactDistanceMoved = exactDistanceMoved;

return distanceAddition / weightedStrainTime;
}
Expand Down
12 changes: 0 additions & 12 deletions osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,26 @@
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;

namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaDifficultyAttributes : DifficultyAttributes
{
/// <summary>
/// The hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
/// <remarks>
/// Rate-adjusting mods do not affect the hit window at all in osu-stable.
/// </remarks>
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; }

public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;

yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
{
base.FromDatabaseAttributes(values, onlineInfo);

StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
}
}
}
31 changes: 1 addition & 30 deletions osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ public class ManiaDifficultyCalculator : DifficultyCalculator
private const double difficulty_multiplier = 0.018;

private readonly bool isForCurrentRuleset;
private readonly double originalOverallDifficulty;

public override int Version => 20241007;

public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.MatchesOnlineID(ruleset);
originalOverallDifficulty = beatmap.BeatmapInfo.Difficulty.OverallDifficulty;
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
Expand All @@ -48,11 +46,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat

ManiaDifficultyAttributes attributes = new ManiaDifficultyAttributes
{
StarRating = skills[0].DifficultyValue() * difficulty_multiplier,
StarRating = skills.OfType<Strain>().Single().DifficultyValue() * difficulty_multiplier,
Mods = mods,
// In osu-stable mania, rate-adjustment mods don't affect the hit window.
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject),
};

Expand Down Expand Up @@ -124,29 +119,5 @@ protected override Mod[] DifficultyAdjustmentMods
}).ToArray();
}
}

private double getHitWindow300(Mod[] mods)
{
if (isForCurrentRuleset)
{
double od = Math.Min(10.0, Math.Max(0, 10.0 - originalOverallDifficulty));
return applyModAdjustments(34 + 3 * od, mods);
}

if (Math.Round(originalOverallDifficulty) > 4)
return applyModAdjustments(34, mods);

return applyModAdjustments(47, mods);

static double applyModAdjustments(double value, Mod[] mods)
{
if (mods.Any(m => m is ManiaModHardRock))
value /= 1.4;
else if (mods.Any(m => m is ManiaModEasy))
value *= 1.4;

return value;
}
}
}
}
18 changes: 9 additions & 9 deletions osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,22 @@ public class OsuDifficultyCalculatorTest : DifficultyCalculatorTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu.Tests";

[TestCase(6.7171144000821119d, 239, "diffcalc-test")]
[TestCase(1.4485749025771304d, 54, "zero-length-sliders")]
[TestCase(0.42630400627180914d, 4, "very-fast-slider")]
[TestCase(6.7331304290522747d, 239, "diffcalc-test")]
[TestCase(1.4602604078137214d, 54, "zero-length-sliders")]
[TestCase(0.43052813047866129d, 4, "very-fast-slider")]
[TestCase(0.14143808967817237d, 2, "nan-slider")]
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
=> base.Test(expectedStarRating, expectedMaxCombo, name);

[TestCase(8.9825709931204205d, 239, "diffcalc-test")]
[TestCase(1.7550169162648608d, 54, "zero-length-sliders")]
[TestCase(0.55231632896800109d, 4, "very-fast-slider")]
[TestCase(9.6779746353001634d, 239, "diffcalc-test")]
[TestCase(1.7691451263718989d, 54, "zero-length-sliders")]
[TestCase(0.55785578988249407d, 4, "very-fast-slider")]
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());

[TestCase(6.7171144000821119d, 239, "diffcalc-test")]
[TestCase(1.4485749025771304d, 54, "zero-length-sliders")]
[TestCase(0.42630400627180914d, 4, "very-fast-slider")]
[TestCase(6.7331304290522747d, 239, "diffcalc-test")]
[TestCase(1.4602604078137214d, 54, "zero-length-sliders")]
[TestCase(0.43052813047866129d, 4, "very-fast-slider")]
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());

Expand Down
48 changes: 29 additions & 19 deletions osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
public static class AimEvaluator
{
private const double wide_angle_multiplier = 1.5;
private const double acute_angle_multiplier = 1.95;
private const double acute_angle_multiplier = 2.6;
private const double slider_multiplier = 1.35;
private const double velocity_change_multiplier = 0.75;
private const double wiggle_multiplier = 1.02;

/// <summary>
/// Evaluates the difficulty of aiming the current object, based on:
Expand Down Expand Up @@ -64,37 +65,44 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
double acuteAngleBonus = 0;
double sliderBonus = 0;
double velocityChangeBonus = 0;
double wiggleBonus = 0;

double aimStrain = currVelocity; // Start strain with regular velocity.

if (Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime)) // If rhythms are the same.
{
if (osuCurrObj.Angle != null && osuLastObj.Angle != null && osuLastLastObj.Angle != null)
if (osuCurrObj.Angle != null && osuLastObj.Angle != null)
{
double currAngle = osuCurrObj.Angle.Value;
double lastAngle = osuLastObj.Angle.Value;
double lastLastAngle = osuLastLastObj.Angle.Value;

// Rewarding angles, take the smaller velocity as base.
double angleBonus = Math.Min(currVelocity, prevVelocity);

wideAngleBonus = calcWideAngleBonus(currAngle);
acuteAngleBonus = calcAcuteAngleBonus(currAngle);

if (DifficultyCalculationUtils.MillisecondsToBPM(osuCurrObj.StrainTime, 2) < 300) // Only buff deltaTime exceeding 300 bpm 1/2.
acuteAngleBonus = 0;
else
{
acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
* Math.Min(angleBonus, diameter * 1.25 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.LazyJumpDistance, radius, diameter) - radius) / radius), 2); // Buff distance exceeding radius up to diameter.
}

// Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3)));
// Penalize acute angles if they're repeated, reducing the penalty as the lastLastAngle gets more obtuse.
acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastLastAngle), 3)));
// Penalize angle repetition.
wideAngleBonus *= 1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3));
acuteAngleBonus *= 0.08 + 0.92 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastAngle), 3)));

// Apply full wide angle bonus for distance more than one diameter
wideAngleBonus *= angleBonus * DifficultyCalculationUtils.Smootherstep(osuCurrObj.LazyJumpDistance, 0, diameter);

// Apply acute angle bonus for BPM above 300 1/2 and distance more than one diameter
acuteAngleBonus *= angleBonus *
DifficultyCalculationUtils.Smootherstep(DifficultyCalculationUtils.MillisecondsToBPM(osuCurrObj.StrainTime, 2), 300, 400) *
DifficultyCalculationUtils.Smootherstep(osuCurrObj.LazyJumpDistance, diameter, diameter * 2);

// Apply wiggle bonus for jumps that are [radius, 3*diameter] in distance, with < 110 angle
// https://www.desmos.com/calculator/dp0v0nvowc
wiggleBonus = angleBonus
* DifficultyCalculationUtils.Smootherstep(osuCurrObj.LazyJumpDistance, radius, diameter)
* Math.Pow(DifficultyCalculationUtils.ReverseLerp(osuCurrObj.LazyJumpDistance, diameter * 3, diameter), 1.8)
* DifficultyCalculationUtils.Smootherstep(currAngle, double.DegreesToRadians(110), double.DegreesToRadians(60))
* DifficultyCalculationUtils.Smootherstep(osuLastObj.LazyJumpDistance, radius, diameter)
* Math.Pow(DifficultyCalculationUtils.ReverseLerp(osuLastObj.LazyJumpDistance, diameter * 3, diameter), 1.8)
* DifficultyCalculationUtils.Smootherstep(lastAngle, double.DegreesToRadians(110), double.DegreesToRadians(60));
}
}

Expand Down Expand Up @@ -122,6 +130,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
}

aimStrain += wiggleBonus * wiggle_multiplier;

// Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger.
aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier);

Expand All @@ -132,8 +142,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
return aimStrain;
}

private static double calcWideAngleBonus(double angle) => Math.Pow(Math.Sin(3.0 / 4 * (Math.Min(5.0 / 6 * Math.PI, Math.Max(Math.PI / 6, angle)) - Math.PI / 6)), 2);
private static double calcWideAngleBonus(double angle) => DifficultyCalculationUtils.Smoothstep(angle, double.DegreesToRadians(40), double.DegreesToRadians(140));

private static double calcAcuteAngleBonus(double angle) => 1 - calcWideAngleBonus(angle);
private static double calcAcuteAngleBonus(double angle) => DifficultyCalculationUtils.Smoothstep(angle, double.DegreesToRadians(140), double.DegreesToRadians(40));
}
}
Loading
Loading