Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the displayed BPM at song select with rate adjust mods #11900

Merged
merged 9 commits into from
Feb 25, 2021
7 changes: 4 additions & 3 deletions osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
Expand Down Expand Up @@ -111,8 +112,8 @@ private void testBeatmapLabels(Ruleset ruleset)

private void testInfoLabels(int expectedCount)
{
AddAssert("check info labels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any());
AddAssert("check info labels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount);
AddAssert("check info labels exists", () => infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Any());
AddAssert("check info labels count", () => infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Count() == expectedCount);
}

[Test]
Expand All @@ -123,7 +124,7 @@ public void TestNullBeatmap()
AddAssert("check default title", () => infoWedge.Info.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title);
AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist);
AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any());
AddAssert("check no info labels", () => !infoWedge.Info.InfoLabelContainer.Children.Any());
AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Any());
}

[Test]
Expand Down
140 changes: 91 additions & 49 deletions osu.Game/Screens/Select/BeatmapInfoWedge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using JetBrains.Annotations;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
Expand All @@ -25,6 +24,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
Expand All @@ -38,7 +38,11 @@ public class BeatmapInfoWedge : VisibilityContainer

private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0);

private readonly IBindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; }

[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; }

[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; }
Expand All @@ -63,11 +67,10 @@ public BeatmapInfoWedge()
};
}

[BackgroundDependencyLoader(true)]
private void load([CanBeNull] Bindable<RulesetInfo> parentRuleset)
[BackgroundDependencyLoader]
private void load()
{
ruleset.BindTo(parentRuleset);
ruleset.ValueChanged += _ => updateDisplay();
ruleset.BindValueChanged(_ => updateDisplay());
}

protected override void PopIn()
Expand Down Expand Up @@ -132,7 +135,7 @@ void removeOldInfo()
return;
}

LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value)
LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value)
{
Shear = -Shear,
Depth = Info?.Depth + 1 ?? 0
Expand Down Expand Up @@ -160,20 +163,25 @@ public class BufferedWedgeInfo : BufferedContainer
public OsuSpriteText ArtistLabel { get; private set; }
public BeatmapSetOnlineStatusPill StatusPill { get; private set; }
public FillFlowContainer MapperContainer { get; private set; }
public FillFlowContainer InfoLabelContainer { get; private set; }

private ILocalisedBindableString titleBinding;
private ILocalisedBindableString artistBinding;
private FillFlowContainer infoLabelContainer;
private Container bpmLabelContainer;

private readonly WorkingBeatmap beatmap;
private readonly RulesetInfo ruleset;
private readonly IReadOnlyList<Mod> mods;
private readonly StarDifficulty starDifficulty;

public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, StarDifficulty difficulty)
private ModSettingChangeTracker settingChangeTracker;

public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList<Mod> mods, StarDifficulty difficulty)
: base(pixelSnapping: true)
{
this.beatmap = beatmap;
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
this.mods = mods;
starDifficulty = difficulty;
}

Expand All @@ -184,7 +192,6 @@ private void load(LocalisationManager localisation)
var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();

CacheDrawnFrameBuffer = true;

RelativeSizeAxes = Axes.Both;

titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title));
Expand Down Expand Up @@ -302,12 +309,11 @@ private void load(LocalisationManager localisation)
AutoSizeAxes = Axes.Both,
Children = getMapper(metadata)
},
InfoLabelContainer = new FillFlowContainer
infoLabelContainer = new FillFlowContainer
{
Margin = new MarginPadding { Top = 20 },
Spacing = new Vector2(20, 0),
AutoSizeAxes = Axes.Both,
Children = getInfoLabels()
}
}
}
Expand All @@ -319,6 +325,8 @@ private void load(LocalisationManager localisation)
// no difficulty means it can't have a status to show
if (beatmapInfo.Version == null)
StatusPill.Hide();

addInfoLabels();
}

private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0
Expand All @@ -335,63 +343,91 @@ private void setMetadata(string source)
ForceRedraw();
}

private InfoLabel[] getInfoLabels()
private void addInfoLabels()
{
var b = beatmap.Beatmap;

List<InfoLabel> labels = new List<InfoLabel>();
if (beatmap.Beatmap?.HitObjects?.Any() != true)
return;

if (b?.HitObjects?.Any() == true)
infoLabelContainer.Children = new Drawable[]
{
labels.Add(new InfoLabel(new BeatmapStatistic
new InfoLabel(new BeatmapStatistic
{
Name = "Length",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
Content = TimeSpan.FromMilliseconds(b.BeatmapInfo.Length).ToString(@"m\:ss"),
}));

labels.Add(new InfoLabel(new BeatmapStatistic
Content = TimeSpan.FromMilliseconds(beatmap.BeatmapInfo.Length).ToString(@"m\:ss"),
}),
bpmLabelContainer = new Container
{
Name = "BPM",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm),
Content = getBPMRange(b),
}));

try
AutoSizeAxes = Axes.Both,
},
new FillFlowContainer
{
IBeatmap playableBeatmap;
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(20, 0),
Children = getRulesetInfoLabels()
}
};

try
{
// Try to get the beatmap with the user's ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty<Mod>());
}
catch (BeatmapInvalidForRulesetException)
{
// Can't be converted to the user's ruleset, so use the beatmap's own ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty<Mod>());
}
settingChangeTracker = new ModSettingChangeTracker(mods);
settingChangeTracker.SettingChanged += _ => refreshBPMLabel();

labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)));
refreshBPMLabel();
}

private InfoLabel[] getRulesetInfoLabels()
{
try
{
IBeatmap playableBeatmap;

try
{
// Try to get the beatmap with the user's ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty<Mod>());
}
catch (Exception e)
catch (BeatmapInvalidForRulesetException)
{
Logger.Error(e, "Could not load beatmap successfully!");
// Can't be converted to the user's ruleset, so use the beatmap's own ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty<Mod>());
}

return playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)).ToArray();
}
catch (Exception e)
{
Logger.Error(e, "Could not load beatmap successfully!");
}

return labels.ToArray();
return Array.Empty<InfoLabel>();
}

private string getBPMRange(IBeatmap beatmap)
private void refreshBPMLabel()
{
double bpmMax = beatmap.ControlPointInfo.BPMMaximum;
double bpmMin = beatmap.ControlPointInfo.BPMMinimum;
var b = beatmap.Beatmap;
if (b == null)
return;

// this doesn't consider mods which apply variable rates, yet.
double rate = 1;
foreach (var mod in mods.OfType<IApplicableToRate>())
rate = mod.ApplyToRate(0, rate);

double bpmMax = b.ControlPointInfo.BPMMaximum * rate;
double bpmMin = b.ControlPointInfo.BPMMinimum * rate;
double mostCommonBPM = 60000 / b.GetMostCommonBeatLength() * rate;

if (Precision.AlmostEquals(bpmMin, bpmMax))
return $"{bpmMin:0}";
string labelText = Precision.AlmostEquals(bpmMin, bpmMax)
? $"{bpmMin:0}"
: $"{bpmMin:0}-{bpmMax:0} (mostly {mostCommonBPM:0})";

bpmLabelContainer.Child = new InfoLabel(new BeatmapStatistic
{
Name = "BPM",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm),
Content = labelText
});

return $"{bpmMin:0}-{bpmMax:0} (mostly {60000 / beatmap.GetMostCommonBeatLength():0})";
ForceRedraw();
}

private OsuSpriteText[] getMapper(BeatmapMetadata metadata)
Expand All @@ -414,6 +450,12 @@ private OsuSpriteText[] getMapper(BeatmapMetadata metadata)
};
}

protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
settingChangeTracker?.Dispose();
}

public class InfoLabel : Container, IHasTooltip
{
public string TooltipText { get; }
Expand Down