Skip to content

Commit 861c6d3

Browse files
authored
Merge pull request #29 from winnerspiros/catch-variable-clockrates-11639420423937482729
Support variable clockrates in Catch difficulty calculation
2 parents e801c35 + df037b0 commit 861c6d3

5 files changed

Lines changed: 45 additions & 18 deletions

File tree

osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(I
5252

5353
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
5454

55+
// Get applicable rate mods upfront to avoid iterating and filtering Mods for every object.
56+
var rateMods = Mods.OfType<IApplicableToRate>().ToList();
57+
5558
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
5659
foreach (var hitObject in CatchBeatmap.GetPalpableObjects(beatmap.HitObjects))
5760
{
@@ -60,7 +63,14 @@ protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(I
6063
continue;
6164

6265
if (lastObject != null)
63-
objects.Add(new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatcherWidth, objects, objects.Count));
66+
{
67+
double objectClockRate = 1;
68+
69+
foreach (var mod in rateMods)
70+
objectClockRate = mod.ApplyToRate(hitObject.StartTime, objectClockRate);
71+
72+
objects.Add(new CatchDifficultyHitObject(hitObject, lastObject, objectClockRate, halfCatcherWidth, objects, objects.Count));
73+
}
6474

6575
lastObject = hitObject;
6676
}
@@ -77,7 +87,7 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo
7787

7888
return new Skill[]
7989
{
80-
new Movement(mods, halfCatcherWidth, clockRate),
90+
new Movement(mods, halfCatcherWidth),
8191
};
8292
}
8393

osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,13 @@ public class CatchDifficultyHitObject : DifficultyHitObject
5959
/// </summary>
6060
public readonly double StrainTime;
6161

62-
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth, List<DifficultyHitObject> objects, int index)
63-
: base(hitObject, lastObject, clockRate, objects, index)
62+
public readonly double CatcherSpeedMultiplier;
63+
64+
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double catcherSpeedMultiplier, float halfCatcherWidth, List<DifficultyHitObject> objects, int index)
65+
: base(hitObject, lastObject, catcherSpeedMultiplier, objects, index)
6466
{
67+
CatcherSpeedMultiplier = catcherSpeedMultiplier;
68+
6569
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
6670
float scalingFactor = NORMALIZED_HALF_CATCHER_WIDTH / halfCatcherWidth;
6771

osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// See the LICENCE file in the repository root for full licence text.
33

44
using osu.Game.Rulesets.Catch.Difficulty.Evaluators;
5+
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
56
using osu.Game.Rulesets.Difficulty.Preprocessing;
67
using osu.Game.Rulesets.Difficulty.Skills;
78
using osu.Game.Rulesets.Mods;
@@ -19,26 +20,15 @@ public class Movement : StrainDecaySkill
1920

2021
protected readonly float HalfCatcherWidth;
2122

22-
/// <summary>
23-
/// The speed multiplier applied to the player's catcher.
24-
/// </summary>
25-
private readonly double catcherSpeedMultiplier;
26-
27-
public Movement(Mod[] mods, float halfCatcherWidth, double clockRate)
23+
public Movement(Mod[] mods, float halfCatcherWidth)
2824
: base(mods)
2925
{
3026
HalfCatcherWidth = halfCatcherWidth;
31-
32-
// In catch, clockrate adjustments do not only affect the timings of hitobjects,
33-
// but also the speed of the player's catcher, which has an impact on difficulty
34-
// TODO: Support variable clockrates caused by mods such as ModTimeRamp
35-
// (perhaps by using IApplicableToRate within the CatchDifficultyHitObject constructor to set a catcher speed for each object before processing)
36-
catcherSpeedMultiplier = clockRate;
3727
}
3828

3929
protected override double StrainValueOf(DifficultyHitObject current)
4030
{
41-
return MovementEvaluator.EvaluateDifficultyOf(current, catcherSpeedMultiplier);
31+
return MovementEvaluator.EvaluateDifficultyOf(current, ((CatchDifficultyHitObject)current).CatcherSpeedMultiplier);
4232
}
4333
}
4434
}

osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,28 @@ public override void SetUpSteps()
7373
{
7474
base.SetUpSteps();
7575

76-
AddUntilStep("wait for mod select removed", () => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Count(), () => Is.Zero);
76+
AddUntilStep("wait for mod select removed", () =>
77+
{
78+
if (this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Any())
79+
{
80+
// This overlay is a bit problematic as it can be present even if the screen that created it has exited.
81+
// If it is present, force close it.
82+
var modSelect = this.ChildrenOfType<MultiplayerUserModSelectOverlay>().First();
83+
modSelect.Hide();
84+
// If it's still visible after hide request (e.g. animation), we still wait.
85+
// But checking Count() implies checking presence in hierarchy or visual state?
86+
// ChildrenOfType checks hierarchy. If Hide() starts fade out, it might still be there.
87+
// But if hierarchy removal is tied to state, we might need to wait more.
88+
// However, base.SetUpSteps() calls ExitAllScreens.
89+
// If the screen is gone, the overlay should be gone unless it's attached to global overlay content?
90+
// MultiplayerMatchSubScreen uses IOverlayManager.RegisterBlockingOverlay. This attaches it to the overlay content.
91+
// So we must ensure it is Unregistered/Hidden.
92+
// MultiplayerMatchSubScreen.OnLeaving hides it.
93+
// If OnLeaving wasn't called, it stays.
94+
return false;
95+
}
96+
return true;
97+
});
7798

7899
AddStep("load match", () =>
79100
{

osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public abstract class DifficultyCalculator
3333
/// </summary>
3434
protected readonly IWorkingBeatmap WorkingBeatmap;
3535

36+
protected IReadOnlyList<Mod> Mods => playableMods ?? Array.Empty<Mod>();
37+
3638
private Mod[] playableMods;
3739
private double clockRate;
3840

0 commit comments

Comments
 (0)