diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs
index e9d26683c3..945a60fb62 100644
--- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs
@@ -114,6 +114,25 @@ namespace osu.Game.Beatmaps
return computeDifficulty(key, beatmapInfo, rulesetInfo);
}
+ ///
+ /// Retrieves the that describes a star rating.
+ ///
+ ///
+ /// For more information, see: https://osu.ppy.sh/help/wiki/Difficulties
+ ///
+ /// The star rating.
+ /// The that best describes .
+ public static DifficultyRating GetDifficultyRating(double starRating)
+ {
+ if (starRating < 2.0) return DifficultyRating.Easy;
+ if (starRating < 2.7) return DifficultyRating.Normal;
+ if (starRating < 4.0) return DifficultyRating.Hard;
+ if (starRating < 5.3) return DifficultyRating.Insane;
+ if (starRating < 6.5) return DifficultyRating.Expert;
+
+ return DifficultyRating.ExpertPlus;
+ }
+
private CancellationTokenSource trackedUpdateCancellationSource;
private readonly List linkedCancellationSources = new List();
@@ -307,5 +326,7 @@ namespace osu.Game.Beatmaps
// Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
}
+
+ public DifficultyRating DifficultyRating => BeatmapDifficultyManager.GetDifficultyRating(Stars);
}
}
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index c5be5810e9..acab525821 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -135,21 +135,7 @@ namespace osu.Game.Beatmaps
public List Scores { get; set; }
[JsonIgnore]
- public DifficultyRating DifficultyRating
- {
- get
- {
- var rating = StarDifficulty;
-
- if (rating < 2.0) return DifficultyRating.Easy;
- if (rating < 2.7) return DifficultyRating.Normal;
- if (rating < 4.0) return DifficultyRating.Hard;
- if (rating < 5.3) return DifficultyRating.Insane;
- if (rating < 6.5) return DifficultyRating.Expert;
-
- return DifficultyRating.ExpertPlus;
- }
- }
+ public DifficultyRating DifficultyRating => BeatmapDifficultyManager.GetDifficultyRating(StarDifficulty);
public string[] SearchableTerms => new[]
{
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index 8a0d981e49..45327d4514 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -2,7 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using System.Threading;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -14,6 +18,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
using osuTK;
using osuTK.Graphics;
@@ -21,9 +26,6 @@ namespace osu.Game.Beatmaps.Drawables
{
public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip
{
- private readonly BeatmapInfo beatmap;
- private readonly RulesetInfo ruleset;
-
private readonly Container iconContainer;
///
@@ -35,23 +37,49 @@ namespace osu.Game.Beatmaps.Drawables
set => iconContainer.Size = value;
}
- public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, bool shouldShowTooltip = true)
+ [NotNull]
+ private readonly BeatmapInfo beatmap;
+
+ [CanBeNull]
+ private readonly RulesetInfo ruleset;
+
+ [CanBeNull]
+ private readonly IReadOnlyList mods;
+
+ private readonly bool shouldShowTooltip;
+ private readonly IBindable difficultyBindable = new Bindable();
+
+ private Drawable background;
+
+ ///
+ /// Creates a new with a given and combination.
+ ///
+ /// The beatmap to show the difficulty of.
+ /// The ruleset to show the difficulty with.
+ /// The mods to show the difficulty with.
+ /// Whether to display a tooltip when hovered.
+ public DifficultyIcon([NotNull] BeatmapInfo beatmap, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true)
+ : this(beatmap, shouldShowTooltip)
+ {
+ this.ruleset = ruleset ?? beatmap.Ruleset;
+ this.mods = mods ?? Array.Empty();
+ }
+
+ ///
+ /// Creates a new that follows the currently-selected ruleset and mods.
+ ///
+ /// The beatmap to show the difficulty of.
+ /// Whether to display a tooltip when hovered.
+ public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true)
{
this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap));
-
- this.ruleset = ruleset ?? beatmap.Ruleset;
- if (shouldShowTooltip)
- TooltipContent = beatmap;
+ this.shouldShowTooltip = shouldShowTooltip;
AutoSizeAxes = Axes.Both;
InternalChild = iconContainer = new Container { Size = new Vector2(20f) };
}
- public ITooltip GetCustomTooltip() => new DifficultyIconTooltip();
-
- public object TooltipContent { get; }
-
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
@@ -70,10 +98,10 @@ namespace osu.Game.Beatmaps.Drawables
Type = EdgeEffectType.Shadow,
Radius = 5,
},
- Child = new Box
+ Child = background = new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = colours.ForDifficultyRating(beatmap.DifficultyRating),
+ Colour = colours.ForDifficultyRating(beatmap.DifficultyRating) // Default value that will be re-populated once difficulty calculation completes
},
},
new ConstrainedIconContainer
@@ -82,16 +110,73 @@ namespace osu.Game.Beatmaps.Drawables
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
- Icon = ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
- }
+ Icon = (ruleset ?? beatmap.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
+ },
+ new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0),
};
+
+ difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForDifficultyRating(difficulty.NewValue.DifficultyRating));
+ }
+
+ public ITooltip GetCustomTooltip() => new DifficultyIconTooltip();
+
+ public object TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null;
+
+ private class DifficultyRetriever : Component
+ {
+ public readonly Bindable StarDifficulty = new Bindable();
+
+ private readonly BeatmapInfo beatmap;
+ private readonly RulesetInfo ruleset;
+ private readonly IReadOnlyList mods;
+
+ private CancellationTokenSource difficultyCancellation;
+
+ [Resolved]
+ private BeatmapDifficultyManager difficultyManager { get; set; }
+
+ public DifficultyRetriever(BeatmapInfo beatmap, RulesetInfo ruleset, IReadOnlyList mods)
+ {
+ this.beatmap = beatmap;
+ this.ruleset = ruleset;
+ this.mods = mods;
+ }
+
+ private IBindable localStarDifficulty;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ difficultyCancellation = new CancellationTokenSource();
+ localStarDifficulty = ruleset != null
+ ? difficultyManager.GetBindableDifficulty(beatmap, ruleset, mods, difficultyCancellation.Token)
+ : difficultyManager.GetBindableDifficulty(beatmap, difficultyCancellation.Token);
+ localStarDifficulty.BindValueChanged(difficulty => StarDifficulty.Value = difficulty.NewValue);
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ difficultyCancellation?.Cancel();
+ }
+ }
+
+ private class DifficultyIconTooltipContent
+ {
+ public readonly BeatmapInfo Beatmap;
+ public readonly IBindable Difficulty;
+
+ public DifficultyIconTooltipContent(BeatmapInfo beatmap, IBindable difficulty)
+ {
+ Beatmap = beatmap;
+ Difficulty = difficulty;
+ }
}
private class DifficultyIconTooltip : VisibilityContainer, ITooltip
{
private readonly OsuSpriteText difficultyName, starRating;
private readonly Box background;
-
private readonly FillFlowContainer difficultyFlow;
public DifficultyIconTooltip()
@@ -159,14 +244,22 @@ namespace osu.Game.Beatmaps.Drawables
background.Colour = colours.Gray3;
}
+ private readonly IBindable starDifficulty = new Bindable();
+
public bool SetContent(object content)
{
- if (!(content is BeatmapInfo beatmap))
+ if (!(content is DifficultyIconTooltipContent iconContent))
return false;
- difficultyName.Text = beatmap.Version;
- starRating.Text = $"{beatmap.StarDifficulty:0.##}";
- difficultyFlow.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating, true);
+ difficultyName.Text = iconContent.Beatmap.Version;
+
+ starDifficulty.UnbindAll();
+ starDifficulty.BindTo(iconContent.Difficulty);
+ starDifficulty.BindValueChanged(difficulty =>
+ {
+ starRating.Text = $"{difficulty.NewValue.Stars:0.##}";
+ difficultyFlow.Colour = colours.ForDifficultyRating(difficulty.NewValue.DifficultyRating, true);
+ }, true);
return true;
}
diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs
index fbad113caa..fcee4c2f1a 100644
--- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Beatmaps.Drawables
public class GroupedDifficultyIcon : DifficultyIcon
{
public GroupedDifficultyIcon(List beatmaps, RulesetInfo ruleset, Color4 counterColour)
- : base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, false)
+ : base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, null, false)
{
AddInternal(new OsuSpriteText
{
diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
index 0015feb26a..f07bd8c3b2 100644
--- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
+++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Screens.Multi.Components
if (item?.Beatmap != null)
{
drawableRuleset.FadeIn(transition_duration);
- drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, item.Ruleset.Value) { Size = new Vector2(height) };
+ drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, item.Ruleset.Value, item.RequiredMods) { Size = new Vector2(height) };
}
else
drawableRuleset.FadeOut(transition_duration);
diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
index b007e0349d..bda00b65b5 100644
--- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
+++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Screens.Multi
private void refresh()
{
- difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value) { Size = new Vector2(32) };
+ difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) };
beatmapText.Clear();
beatmapText.AddLink(Item.Beatmap.ToString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString());