Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
1b230b3
Remove drain rate attribute (#35817)
Givikap120 Nov 29, 2025
346461f
Skip score-based miss count estimation for ScoreV2 (#35962)
Givikap120 Dec 13, 2025
16c1adf
Use `DifficultyCalculationUtils.Norm` (#36064)
stanriders Dec 18, 2025
99daec3
Simplify osu! star rating calculations (#36063)
stanriders Dec 19, 2025
90e820b
Uses real distance in speed instead of mishandling variables (#35387)
Finadoggie Dec 22, 2025
906df15
change FL calc depending on HD settings (#33019)
Givikap120 Dec 22, 2025
6356d50
Use number calculated at runtime from DecayWeight (#33733)
Finadoggie Dec 23, 2025
d150e27
Reduce calls to `DifficultyValue` where possible (#36112)
tsunyoku Dec 23, 2025
a07d7ad
Extend `Skill` to include `ObjectDifficulties` (#36152)
stanriders Dec 28, 2025
c644172
Add `HarmonicSkill` (#36153)
tsunyoku Dec 28, 2025
dd26162
Change the doubletapness scaling to be more punishing on the low end …
stanriders Dec 29, 2025
bcaf35d
Improve fullcombo threshold formula (#35724)
Givikap120 Jan 3, 2026
7fc24c6
Fix legacy score base multiplier being calculated using modded diffic…
tsunyoku Jan 17, 2026
cda52cd
Add comment for wiggle bonus multiplier (#36373)
kwotaq Jan 18, 2026
2876ceb
Move strain's influence in the difficulty of a note based on deltaTim…
Natelytle Jan 22, 2026
520173e
Replace Speed skill strain summation with a per-note harmonic sum (#3…
kwotaq Jan 24, 2026
fb8d198
Improve angle calculation to better represent the path taken on slide…
nisico3 Jan 24, 2026
c25820a
Use correct object for the aim slider bonus (#36216)
stanriders Jan 24, 2026
562f1f8
Replace current AR and HD bonuses with a new Reading skill on the osu…
kwotaq Jan 25, 2026
0afcebe
Move speed distance bonus to aim (#36168)
stanriders Jan 25, 2026
02b76db
Move TD difficulty reduction to Aim (#36476)
stanriders Jan 26, 2026
ae43d3e
Use deviation-based Speed accuracy scaling (#36475)
stanriders Jan 26, 2026
ea87e82
Make reading and flashlight pp to be LP summed (#36464)
Givikap120 Jan 26, 2026
aefe31d
Make sliderless aim in fact sliderless (#29993)
Givikap120 Jan 27, 2026
f57a271
Undo slider high cs bonus hotfix (#36513)
stanriders Jan 29, 2026
8be9317
Fix flashlight having near-zero bonus when reading difficulty is much…
Givikap120 Jan 29, 2026
e9974ba
Adjust speed deviation scaling to be more harsh on low end (#36554)
Givikap120 Jan 30, 2026
355addc
Nerf aim strain for objects <radius (#36558)
tsunyoku Jan 31, 2026
ee0ac3e
Simplify hidden difficulty calculation (#36465)
Givikap120 Feb 1, 2026
8c180b0
Change wide bonus distance reduction to be consistent with `SpeedAim.…
stanriders Feb 4, 2026
52be311
Add a bunch of rhythm improvements (#36569)
stanriders Feb 4, 2026
4eec06c
Add real sliderend check in rhythm (#36593)
stanriders Feb 5, 2026
0f70903
only nerf total cognition sum when reading > flashlight (#36599)
molneya Feb 8, 2026
0d42b77
Return zero rhythm difficulty for `Swell` and `DrumRoll` (#36628)
stanriders Feb 8, 2026
25d2c04
Add early returns in `HarmonicSkill` and `OsuDifficultyCalculator` (#…
stanriders Feb 10, 2026
3a46319
Nerf HD perfect stack reward (#36623)
Givikap120 Feb 10, 2026
c8318d6
Refactor `DifficultyHitObject` to include `ClockRate` and `HitWindows…
stanriders Feb 10, 2026
a166d00
Change accuracy pp to be uncapped (#36669)
stanriders Feb 14, 2026
62fdd42
Adjust wide angle bonus to be closer to SpeedAim distance scaling (#3…
stanriders Feb 15, 2026
de50789
Adjust RX-related multipliers (#36697)
stanriders Feb 18, 2026
716cd8d
Fix Aim's `skillMultiplierTotal` being applied incorrectly (#36699)
stanriders Feb 18, 2026
a14e87f
Q1 2026 osu! Balancing pass (#36734)
stanriders Feb 22, 2026
2d6fa65
Use full hitwindow in taiko Rhythm skill (#36671)
stanriders Feb 26, 2026
f241d9c
Fix combined slider velocity calculation (#36773)
stanriders Mar 2, 2026
b1227e1
Aim strain influence reduction (#36792)
stanriders Mar 2, 2026
020e6fa
Reduce strain influence a bit more (#36813)
stanriders Mar 4, 2026
b945e2e
Adjust wide aim bonus cutoff after SpeedAim changes (#36835)
stanriders Mar 5, 2026
ab2c91e
Slightly increase TD penalty (#36855)
stanriders Mar 6, 2026
8efb10d
Increase high cs bonus in SpeedAim (#36856)
stanriders Mar 6, 2026
c2e9b05
Add basic flow evaluation (#36902)
stanriders Mar 9, 2026
f3d97c0
Penalize angle repetition depending on the difference between vectors…
kwotaq Mar 9, 2026
de0e2c3
Merge branch 'master' into pp-dev
peppy Mar 10, 2026
e9f5975
Fix Hidden mod perfect stacks buff for osu! reading difficulty (#36918)
Rian8337 Mar 10, 2026
c2c8b90
Remove `OsuStrainSkill` (#36917)
stanriders Mar 10, 2026
29e089c
Organize osu! difficulty evaluators a bit better (#36921)
stanriders Mar 10, 2026
6c9a729
Adjust `Speed` harmonic summation params (#36923)
stanriders Mar 10, 2026
b0ee82d
Remove `OsuStrainUtils` (#36924)
tsunyoku Mar 11, 2026
1e01001
Use current object's `Preempt` when retrieving past visible objects i…
Rian8337 Mar 12, 2026
5b8067a
Restore precision buff in Agility (#36968)
stanriders Mar 16, 2026
00c017e
Merge branch 'master' into pp-dev
smoogipoo Mar 16, 2026
abf1a00
Apply a few minor fixes to flow aim evaluation (#36999)
tsunyoku Mar 16, 2026
fc82dac
Remove slider velocity considerations from previous object velocity (…
stanriders Mar 16, 2026
09bcef1
Adjust Aim RX multiplier (#36985)
stanriders Mar 16, 2026
fadb1c3
Calculate clock time when creating difficulty hit objects (#36962)
stanriders Mar 16, 2026
b538c1d
Change the aim scaling slightly (#36967)
stanriders Mar 16, 2026
c7eeccc
Make miss penalty slightly harsher on first misses (#37040)
stanriders Mar 22, 2026
22ac12c
Undo Agility strain influence rescaling (#37081)
stanriders Mar 24, 2026
0071c82
Adjust high CS bonuses after all the distance-time rescaling (#37088)
stanriders Mar 25, 2026
0f37874
Increase Aim strain decay base (#37099)
stanriders Mar 25, 2026
b31d1b3
Account for alternating angles in reading angle factor (#36466)
Givikap120 Mar 26, 2026
b0047e4
Use variable-length strains to fix chunking-related issues (#33351)
Finadoggie Mar 27, 2026
4ce4bca
Very slight overall aim buff (#37134)
stanriders Mar 27, 2026
e082550
Aim balancing (#37147)
stanriders Mar 29, 2026
c4ef081
Refactor Reading strain to match the rest of the skills (#37010)
kwotaq Mar 29, 2026
f6223d1
Move Aim mod-related difficulty adjustments to after the probability …
stanriders Apr 3, 2026
5049ac8
Refactor SnapAimEvaluator bonuses for better readability (#37119)
kwotaq Apr 12, 2026
b20b367
Fix snap angle repetition (#37286)
stanriders Apr 13, 2026
a057138
Fix low distance snap/flow relation (#37306)
stanriders Apr 15, 2026
5aff5a4
Rebalance TC Bonus (#36555)
piiidosu Apr 16, 2026
f204781
Rescale wide snap aim bonus (#37411)
stanriders Apr 20, 2026
af9f9e7
Use Mehs in the okAdjustment (#37264)
Givikap120 Apr 21, 2026
a8628fd
Remove flow acute bonus velocity sqrt (#37436)
stanriders Apr 21, 2026
8d079a6
Nerf linear spacings in osu!catch (#37287)
wulpine Apr 23, 2026
5b073e6
Standardize difficulty variable naming (#37460)
Natelytle Apr 24, 2026
b30f10f
Move mod-based difficulty adjustments to skills (#37621)
stanriders May 4, 2026
bf405fb
Another batch of small rhythm evaluation fixes (#37609)
stanriders May 4, 2026
5a801e2
Add distance factor to anti-doubletap (#37636)
Givikap120 May 6, 2026
2b1d4aa
Remove accuracy bonus when flashlight is enabled (#36900)
molneya May 6, 2026
d18edb5
Make Ok adjustment formula more stable (#37263)
Givikap120 May 6, 2026
c587106
Refactor aim/speed sliderbreak estimation calculation calls (#37654)
stanriders May 6, 2026
a84f5a7
osu!taiko penalty for frequent rhythm changes with long gaps (#37200)
buyaspacecube May 13, 2026
c0c952e
Merge branch 'master' into pp-dev
smoogipoo May 18, 2026
9a734d4
Move remaining osu! rating adjustments to skills (#37623)
stanriders May 19, 2026
b073cdc
Fix `sliderFactor` using incorrect aim rating conversion curve (#37624)
stanriders May 19, 2026
6aaa10c
Update difficulty calculation tests (#37829)
tsunyoku May 20, 2026
555c099
Merge branch 'master' into pp-dev
peppy Jun 15, 2026
017f57b
Use constant difficulty for single-note rhythm islands (#38082)
stanriders Jun 15, 2026
c81c5b8
Merge branch 'master' into pp-dev
peppy Jun 22, 2026
149bf92
Remove storage of epsilon per island
peppy Jun 24, 2026
af2b163
Fix incorrect null checks
peppy Jun 24, 2026
7be8348
Fix incorrectly defined constants
peppy Jun 24, 2026
3a0d78a
Refactor `CalculateNestedScorePerObject` to avoid array storage and m…
peppy Jun 25, 2026
06577a2
Initial tidy pass of `RhythmEvaluator`
peppy Jun 25, 2026
c90e246
Pre-allocate `DifficultyHitObject`s list length of known length
peppy Jun 25, 2026
967ac38
Remove dead code
peppy Jun 26, 2026
30ab9c9
Rename weird "doubletapness" to something more appropriate
peppy Jun 26, 2026
209634c
"Note" -> "Object"
peppy Jun 26, 2026
78c11bf
Reduce storage overheads of `OsuDifficultyHitObject`
peppy Jun 26, 2026
c4e770d
Add note about `VariableLengthStrainSkill` upgrade path
peppy Jun 26, 2026
22e5cbf
Attempt to fix xmldoc references
peppy Jun 28, 2026
4d5b268
Remove unnecessary `virtual` usage
peppy Jun 26, 2026
26c38a5
Avoid one final list copy
peppy Jun 26, 2026
4bc6a97
Use more efficient `Math.Pow` when exponent is an integer
peppy Jun 28, 2026
0413705
Simple LINQ free optimisation to `Norm()`
peppy Jun 28, 2026
f66eb58
Avoid duplicating list when not required (see `Speed` skill use case)
peppy Jun 26, 2026
9755da9
Cache `HitWindowGreat` to avoid constant fetches
peppy Jun 26, 2026
ec52f0d
Tidy up and reorder strain storage to avoid list copy overhead
peppy Jun 26, 2026
4b44876
More inlining and faster smoothstep with baked arguments
peppy Jun 28, 2026
ba4a24e
Remove impossible null check
peppy Jun 28, 2026
8de316e
Use quicker `Pow` in optimised `Norm` implementation
peppy Jun 28, 2026
3441681
Remove LINQ overhead from `RhythmEvaluator`
peppy Jun 28, 2026
4dfdf44
`pp-dev` cleanup pass (#38179)
stanriders Jun 29, 2026
081f497
Fix outdated benchmark code
peppy Jun 29, 2026
b7caf49
Add Add documentation of `Pow` optimisations
peppy Jun 29, 2026
00d08fe
Merge branch 'master' into pp-dev
peppy Jun 30, 2026
c265409
Decompose snap aim evaluator into several methods
tsunyoku Jun 30, 2026
78ba576
Move slider checks outside of slider bonus method
tsunyoku Jul 2, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ public EmptyFreeformDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap b
{
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
{
return new DifficultyAttributes(mods, 0);
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods) => Enumerable.Empty<DifficultyHitObject>();

protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => Array.Empty<Skill>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ public PippidonDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatma
{
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
{
return new DifficultyAttributes(mods, 0);
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods) => Enumerable.Empty<DifficultyHitObject>();

protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => Array.Empty<Skill>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ public EmptyScrollingDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap
{
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
{
return new DifficultyAttributes(mods, 0);
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods) => Enumerable.Empty<DifficultyHitObject>();

protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => Array.Empty<Skill>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ public PippidonDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatma
{
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
{
return new DifficultyAttributes(mods, 0);
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods) => Enumerable.Empty<DifficultyHitObject>();

protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => Array.Empty<Skill>();
}
}
36 changes: 36 additions & 0 deletions osu.Game.Benchmarks/BenchmarkMathPow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using BenchmarkDotNet.Attributes;
using osu.Game.Rulesets.Difficulty.Utils;

namespace osu.Game.Benchmarks
{
public class BenchmarkMathPow : BenchmarkTest
{
[Params(0, 1, 1.25, 2.0, 3, 5)]
public double Exponent { get; set; }

[Benchmark]
public double MathPow()
{
return Math.Pow(1.299995, Exponent);
}

[Benchmark]
public double DiffUtilsPowDouble()
{
return DiffUtils.Pow(1.299995, Exponent);
}

[Benchmark]
public double DiffUtilsPowInt()
{
if ((int)Exponent != Exponent)
throw new NotSupportedException();

return DiffUtils.Pow(1.299995, (int)Exponent);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ public class CatchDifficultyCalculatorTest : DifficultyCalculatorTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch.Tests";

[TestCase(4.0505463516206195d, 127, "diffcalc-test")]
[TestCase(4.039861734717169d, 127, "diffcalc-test")]
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
=> base.Test(expectedStarRating, expectedMaxCombo, name);

[TestCase(5.1696411260785498d, 127, "diffcalc-test")]
[TestCase(5.1527173897800873d, 127, "diffcalc-test")]
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new CatchModDoubleTime());

Expand Down
25 changes: 13 additions & 12 deletions osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,22 @@
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Utils;

namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyCalculator : DifficultyCalculator
{
private const double difficulty_multiplier = 4.59;

private float halfCatcherWidth;

public override int Version => 20251020;

public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
{
if (beatmap.HitObjects.Count == 0)
return new CatchDifficultyAttributes { Mods = mods };
Expand All @@ -46,11 +45,18 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
return attributes;
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods)
{
CatchHitObject? lastObject = null;

List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
List<DifficultyHitObject> objects = new List<DifficultyHitObject>(beatmap.HitObjects.Count);

double clockRate = ModUtils.CalculateRateWithMods(mods);

float halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) * 0.5f;

// For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay.
halfCatcherWidth *= 1 - (Math.Max(0, beatmap.Difficulty.CircleSize - 5.5f) * 0.0625f);

// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
foreach (var hitObject in CatchBeatmap.GetPalpableObjects(beatmap.HitObjects))
Expand All @@ -68,16 +74,11 @@ protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(I
return objects;
}

protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
{
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) * 0.5f;

// For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay.
halfCatcherWidth *= 1 - (Math.Max(0, beatmap.Difficulty.CircleSize - 5.5f) * 0.0625f);

return new Skill[]
{
new Movement(mods, halfCatcherWidth, clockRate),
new Movement(mods),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
Expand Down Expand Up @@ -37,7 +38,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
numMiss = score.GetCountMiss() ?? 0; // HitResult.Miss PLUS HitResult.LargeTickMiss

// We are heavily relying on aim in catch the beat
double value = Math.Pow(5.0 * Math.Max(1.0, catchAttributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
double value = DiffUtils.Pow(5.0 * Math.Max(1.0, catchAttributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;

// Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo
int numTotalHits = totalComboHits();
Expand All @@ -47,11 +48,11 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
(numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
value *= lengthBonus;

value *= Math.Pow(0.97, numMiss);
value *= DiffUtils.Pow(0.97, numMiss);

// Combo scaling
if (catchAttributes.MaxCombo > 0)
value *= Math.Min(Math.Pow(score.MaxCombo, 0.35) / Math.Pow(catchAttributes.MaxCombo, 0.35), 1.0);
value *= Math.Min(DiffUtils.Pow(score.MaxCombo, 0.35) / DiffUtils.Pow(catchAttributes.MaxCombo, 0.35), 1.0);

var difficulty = score.BeatmapInfo!.Difficulty.Clone();

Expand Down Expand Up @@ -86,7 +87,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
if (score.Mods.Any(m => m is ModFlashlight))
value *= 1.35 * lengthBonus;

value *= Math.Pow(accuracy(), 5.5);
value *= DiffUtils.Pow(accuracy(), 5.5);

if (score.Mods.Any(m => m is ModNoFail))
value *= Math.Max(0.90, 1.0 - 0.02 * numMiss);
Expand Down
37 changes: 33 additions & 4 deletions osu.Game.Rulesets.Catch/Difficulty/Evaluators/MovementEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@
using System;
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Utils;

namespace osu.Game.Rulesets.Catch.Difficulty.Evaluators
{
public static class MovementEvaluator
{
private const double direction_change_bonus = 21.0;

public static double EvaluateDifficultyOf(DifficultyHitObject current, double catcherSpeedMultiplier)
public static double EvaluateDifficultyOf(DifficultyHitObject current)
{
var catchCurrent = (CatchDifficultyHitObject)current;
var catchLast = (CatchDifficultyHitObject)current.Previous(0);
var catchLastLast = (CatchDifficultyHitObject)current.Previous(1);

// In catch, clockrate adjustments do not only affect the timings of hitobjects,
// but also the speed of the player's catcher, which has an impact on difficulty
double catcherSpeedMultiplier = current.ClockRate;

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

double distanceAddition = (Math.Pow(Math.Abs(catchCurrent.DistanceMoved), 1.3) / 510);
double distanceAddition = (DiffUtils.Pow(Math.Abs(catchCurrent.DistanceMoved), 1.3) / 510);
double sqrtStrain = Math.Sqrt(weightedStrainTime);

double edgeDashBonus = 0;
Expand All @@ -32,22 +37,46 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, double ca
double bonusFactor = Math.Min(50, Math.Abs(catchCurrent.DistanceMoved)) / 50;
double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(catchLast.DistanceMoved)) / 70, 0.38);

distanceAddition += direction_change_bonus / Math.Sqrt(catchLast.StrainTime + 16) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0);
distanceAddition += direction_change_bonus / Math.Sqrt(catchLast.StrainTime + 16) * bonusFactor * antiflowFactor * Math.Max(1 - DiffUtils.Pow(weightedStrainTime / 1000, 3), 0);
}

// Base bonus for every movement, giving some weight to streams.
distanceAddition += 12.5 * Math.Min(Math.Abs(catchCurrent.DistanceMoved), CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 2)
/ (CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 6) / sqrtStrain;
}

// Linear spacing nerf.
double linearSpacingCount = 0;

for (int i = 0; i < Math.Min(current.Index, 10); i++)
{
var catchPrevObj = (CatchDifficultyHitObject)catchCurrent.Previous(i);

// Only same direction movements matter as they do not take any additional inputs.
if (Math.Sign(catchCurrent.DistanceMoved) != Math.Sign(catchPrevObj.DistanceMoved) || catchCurrent.DistanceMoved == 0 || catchPrevObj.DistanceMoved == 0)
break;

double currentSpacing = Math.Abs(catchCurrent.DistanceMoved / catchCurrent.StrainTime);
double prevSpacing = Math.Abs(catchPrevObj.DistanceMoved / catchPrevObj.StrainTime);

double relativeDifference = Math.Abs(currentSpacing / prevSpacing - 1);

if (relativeDifference > 0.05)
break;

linearSpacingCount++;
}

distanceAddition *= DiffUtils.Pow(0.7, linearSpacingCount);

// Bonus for edge dashes.
if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f)
{
if (!catchCurrent.LastObject.HyperDash)
edgeDashBonus += 5.7;

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
* DiffUtils.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
Expand Down
18 changes: 2 additions & 16 deletions osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,14 @@ public class Movement : StrainDecaySkill

protected override int SectionLength => 750;

protected readonly float HalfCatcherWidth;

/// <summary>
/// The speed multiplier applied to the player's catcher.
/// </summary>
private readonly double catcherSpeedMultiplier;

public Movement(Mod[] mods, float halfCatcherWidth, double clockRate)
public Movement(Mod[] mods)
: base(mods)
{
HalfCatcherWidth = halfCatcherWidth;

// In catch, clockrate adjustments do not only affect the timings of hitobjects,
// but also the speed of the player's catcher, which has an impact on difficulty
// TODO: Support variable clockrates caused by mods such as ModTimeRamp
// (perhaps by using IApplicableToRate within the CatchDifficultyHitObject constructor to set a catcher speed for each object before processing)
catcherSpeedMultiplier = clockRate;
}

protected override double StrainValueOf(DifficultyHitObject current)
{
return MovementEvaluator.EvaluateDifficultyOf(current, catcherSpeedMultiplier);
return MovementEvaluator.EvaluateDifficultyOf(current);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current)
// 0.0 +--------+-+---------------> Release Difference / ms
// release_threshold
if (isOverlapping)
holdAddition = DifficultyCalculationUtils.Logistic(x: closestEndTime, multiplier: 0.27, midpointOffset: release_threshold);
holdAddition = DiffUtils.Logistic(x: closestEndTime, multiplier: 0.27, midpointOffset: release_threshold);

return (1 + holdAddition) * holdFactor;
}
Expand Down
11 changes: 7 additions & 4 deletions osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Utils;

namespace osu.Game.Rulesets.Mania.Difficulty
{
Expand All @@ -36,7 +37,7 @@ public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.MatchesOnlineID(ruleset);
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
{
if (beatmap.HitObjects.Count == 0)
return new ManiaDifficultyAttributes { Mods = mods };
Expand All @@ -62,14 +63,16 @@ private static int maxComboForObject(HitObject hitObject)
return 1;
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods)
{
var sortedObjects = beatmap.HitObjects.ToArray();
int totalColumns = ((ManiaBeatmap)beatmap).TotalColumns;

double clockRate = ModUtils.CalculateRateWithMods(mods);

LegacySortHelper<HitObject>.Sort(sortedObjects, Comparer<HitObject>.Create((a, b) => (int)Math.Round(a.StartTime) - (int)Math.Round(b.StartTime)));

List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
List<DifficultyHitObject> objects = new List<DifficultyHitObject>(beatmap.HitObjects.Count);
List<DifficultyHitObject>[] perColumnObjects = new List<DifficultyHitObject>[totalColumns];

for (int column = 0; column < totalColumns; column++)
Expand All @@ -88,7 +91,7 @@ protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(I
// Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required.
protected override IEnumerable<DifficultyHitObject> SortObjects(IEnumerable<DifficultyHitObject> input) => input;

protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[]
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{
new Strain(mods, ((ManiaBeatmap)Beatmap).TotalColumns)
};
Expand Down
Loading
Loading