Merge pull request #10306 from smoogipoo/dynamic-difficulty-icon

This commit is contained in:
Dean Herbert 2020-10-05 19:40:18 +09:00 committed by GitHub
commit e20c28f166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 139 additions and 39 deletions

View File

@ -114,6 +114,25 @@ namespace osu.Game.Beatmaps
return computeDifficulty(key, beatmapInfo, rulesetInfo); return computeDifficulty(key, beatmapInfo, rulesetInfo);
} }
/// <summary>
/// Retrieves the <see cref="DifficultyRating"/> that describes a star rating.
/// </summary>
/// <remarks>
/// For more information, see: https://osu.ppy.sh/help/wiki/Difficulties
/// </remarks>
/// <param name="starRating">The star rating.</param>
/// <returns>The <see cref="DifficultyRating"/> that best describes <paramref name="starRating"/>.</returns>
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 CancellationTokenSource trackedUpdateCancellationSource;
private readonly List<CancellationTokenSource> linkedCancellationSources = new List<CancellationTokenSource>(); private readonly List<CancellationTokenSource> linkedCancellationSources = new List<CancellationTokenSource>();
@ -307,5 +326,7 @@ namespace osu.Game.Beatmaps
// Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
} }
public DifficultyRating DifficultyRating => BeatmapDifficultyManager.GetDifficultyRating(Stars);
} }
} }

View File

@ -135,21 +135,7 @@ namespace osu.Game.Beatmaps
public List<ScoreInfo> Scores { get; set; } public List<ScoreInfo> Scores { get; set; }
[JsonIgnore] [JsonIgnore]
public DifficultyRating DifficultyRating public DifficultyRating DifficultyRating => BeatmapDifficultyManager.GetDifficultyRating(StarDifficulty);
{
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 string[] SearchableTerms => new[] public string[] SearchableTerms => new[]
{ {

View File

@ -2,7 +2,11 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Threading;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -14,6 +18,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -21,9 +26,6 @@ namespace osu.Game.Beatmaps.Drawables
{ {
public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip
{ {
private readonly BeatmapInfo beatmap;
private readonly RulesetInfo ruleset;
private readonly Container iconContainer; private readonly Container iconContainer;
/// <summary> /// <summary>
@ -35,23 +37,49 @@ namespace osu.Game.Beatmaps.Drawables
set => iconContainer.Size = value; 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<Mod> mods;
private readonly bool shouldShowTooltip;
private readonly IBindable<StarDifficulty> difficultyBindable = new Bindable<StarDifficulty>();
private Drawable background;
/// <summary>
/// Creates a new <see cref="DifficultyIcon"/> with a given <see cref="RulesetInfo"/> and <see cref="Mod"/> combination.
/// </summary>
/// <param name="beatmap">The beatmap to show the difficulty of.</param>
/// <param name="ruleset">The ruleset to show the difficulty with.</param>
/// <param name="mods">The mods to show the difficulty with.</param>
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
public DifficultyIcon([NotNull] BeatmapInfo beatmap, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList<Mod> mods, bool shouldShowTooltip = true)
: this(beatmap, shouldShowTooltip)
{
this.ruleset = ruleset ?? beatmap.Ruleset;
this.mods = mods ?? Array.Empty<Mod>();
}
/// <summary>
/// Creates a new <see cref="DifficultyIcon"/> that follows the currently-selected ruleset and mods.
/// </summary>
/// <param name="beatmap">The beatmap to show the difficulty of.</param>
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true)
{ {
this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap)); this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap));
this.shouldShowTooltip = shouldShowTooltip;
this.ruleset = ruleset ?? beatmap.Ruleset;
if (shouldShowTooltip)
TooltipContent = beatmap;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; InternalChild = iconContainer = new Container { Size = new Vector2(20f) };
} }
public ITooltip GetCustomTooltip() => new DifficultyIconTooltip();
public object TooltipContent { get; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
@ -70,10 +98,10 @@ namespace osu.Game.Beatmaps.Drawables
Type = EdgeEffectType.Shadow, Type = EdgeEffectType.Shadow,
Radius = 5, Radius = 5,
}, },
Child = new Box Child = background = new Box
{ {
RelativeSizeAxes = Axes.Both, 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 new ConstrainedIconContainer
@ -82,16 +110,73 @@ namespace osu.Game.Beatmaps.Drawables
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, 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) // 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> StarDifficulty = new Bindable<StarDifficulty>();
private readonly BeatmapInfo beatmap;
private readonly RulesetInfo ruleset;
private readonly IReadOnlyList<Mod> mods;
private CancellationTokenSource difficultyCancellation;
[Resolved]
private BeatmapDifficultyManager difficultyManager { get; set; }
public DifficultyRetriever(BeatmapInfo beatmap, RulesetInfo ruleset, IReadOnlyList<Mod> mods)
{
this.beatmap = beatmap;
this.ruleset = ruleset;
this.mods = mods;
}
private IBindable<StarDifficulty> 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<StarDifficulty> Difficulty;
public DifficultyIconTooltipContent(BeatmapInfo beatmap, IBindable<StarDifficulty> difficulty)
{
Beatmap = beatmap;
Difficulty = difficulty;
}
} }
private class DifficultyIconTooltip : VisibilityContainer, ITooltip private class DifficultyIconTooltip : VisibilityContainer, ITooltip
{ {
private readonly OsuSpriteText difficultyName, starRating; private readonly OsuSpriteText difficultyName, starRating;
private readonly Box background; private readonly Box background;
private readonly FillFlowContainer difficultyFlow; private readonly FillFlowContainer difficultyFlow;
public DifficultyIconTooltip() public DifficultyIconTooltip()
@ -159,14 +244,22 @@ namespace osu.Game.Beatmaps.Drawables
background.Colour = colours.Gray3; background.Colour = colours.Gray3;
} }
private readonly IBindable<StarDifficulty> starDifficulty = new Bindable<StarDifficulty>();
public bool SetContent(object content) public bool SetContent(object content)
{ {
if (!(content is BeatmapInfo beatmap)) if (!(content is DifficultyIconTooltipContent iconContent))
return false; return false;
difficultyName.Text = beatmap.Version; difficultyName.Text = iconContent.Beatmap.Version;
starRating.Text = $"{beatmap.StarDifficulty:0.##}";
difficultyFlow.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating, true); 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; return true;
} }

View File

@ -20,7 +20,7 @@ namespace osu.Game.Beatmaps.Drawables
public class GroupedDifficultyIcon : DifficultyIcon public class GroupedDifficultyIcon : DifficultyIcon
{ {
public GroupedDifficultyIcon(List<BeatmapInfo> beatmaps, RulesetInfo ruleset, Color4 counterColour) public GroupedDifficultyIcon(List<BeatmapInfo> 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 AddInternal(new OsuSpriteText
{ {

View File

@ -60,7 +60,7 @@ namespace osu.Game.Screens.Multi.Components
if (item?.Beatmap != null) if (item?.Beatmap != null)
{ {
drawableRuleset.FadeIn(transition_duration); 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 else
drawableRuleset.FadeOut(transition_duration); drawableRuleset.FadeOut(transition_duration);

View File

@ -103,7 +103,7 @@ namespace osu.Game.Screens.Multi
private void refresh() 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.Clear();
beatmapText.AddLink(Item.Beatmap.ToString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); beatmapText.AddLink(Item.Beatmap.ToString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString());