From 917e8fc3ba041d40e09396f39f020d2b508ac16e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 00:53:01 +0900 Subject: [PATCH 1/5] Add difficulty rating to StarDifficulty --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index e9d26683c3..159a229499 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs @@ -307,5 +307,19 @@ namespace osu.Game.Beatmaps // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) } + + public DifficultyRating DifficultyRating + { + get + { + if (Stars < 2.0) return DifficultyRating.Easy; + if (Stars < 2.7) return DifficultyRating.Normal; + if (Stars < 4.0) return DifficultyRating.Hard; + if (Stars < 5.3) return DifficultyRating.Insane; + if (Stars < 6.5) return DifficultyRating.Expert; + + return DifficultyRating.ExpertPlus; + } + } } } From fde00d343197d16ed0140d412b4fa4017e369907 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 00:53:25 +0900 Subject: [PATCH 2/5] Make DifficultyIcon support dynamic star rating --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 135 +++++++++++++++--- .../Drawables/GroupedDifficultyIcon.cs | 2 +- .../Screens/Multi/Components/ModeTypeInfo.cs | 2 +- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 2 +- 4 files changed, 117 insertions(+), 24 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 8a0d981e49..d5e4b13a84 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 = 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 : Drawable + { + 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()); From 2213db20886ab1952bf33dc370cb67efa3b0e681 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 00:59:41 +0900 Subject: [PATCH 3/5] Use the given ruleset by default --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index d5e4b13a84..9ffe813187 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -110,7 +110,7 @@ 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 = beatmap.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), }; From 62b55c4c9cb57eb436c8a3a4447f6f20c691dade Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 20:50:47 +0900 Subject: [PATCH 4/5] Use static method, add xmldoc + link to wiki --- osu.Game/Beatmaps/BeatmapDifficultyManager.cs | 33 +++++++++++-------- osu.Game/Beatmaps/BeatmapInfo.cs | 16 +-------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs index 159a229499..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(); @@ -308,18 +327,6 @@ namespace osu.Game.Beatmaps // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) } - public DifficultyRating DifficultyRating - { - get - { - if (Stars < 2.0) return DifficultyRating.Easy; - if (Stars < 2.7) return DifficultyRating.Normal; - if (Stars < 4.0) return DifficultyRating.Hard; - if (Stars < 5.3) return DifficultyRating.Insane; - if (Stars < 6.5) return DifficultyRating.Expert; - - return DifficultyRating.ExpertPlus; - } - } + 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[] { From 40c153e705f2bcb9cbcfa96615fe853edfd62a01 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 21:39:40 +0900 Subject: [PATCH 5/5] Use component instead of drawable --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 9ffe813187..45327d4514 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -122,7 +122,7 @@ namespace osu.Game.Beatmaps.Drawables public object TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null; - private class DifficultyRetriever : Drawable + private class DifficultyRetriever : Component { public readonly Bindable StarDifficulty = new Bindable();