1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 12:42:54 +08:00

Merge pull request #11900 from peppy/update-bpm-with-mods

Update the displayed BPM at song select with rate adjust mods
This commit is contained in:
Dean Herbert 2021-02-26 00:25:55 +09:00 committed by GitHub
commit a792c3f13c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 95 additions and 52 deletions

View File

@ -7,6 +7,7 @@ using JetBrains.Annotations;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -111,8 +112,8 @@ namespace osu.Game.Tests.Visual.SongSelect
private void testInfoLabels(int expectedCount) private void testInfoLabels(int expectedCount)
{ {
AddAssert("check info labels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any()); AddAssert("check info labels exists", () => infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Any());
AddAssert("check info labels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount); AddAssert("check info labels count", () => infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Count() == expectedCount);
} }
[Test] [Test]
@ -123,7 +124,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("check default title", () => infoWedge.Info.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title); 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 default artist", () => infoWedge.Info.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist);
AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); 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] [Test]

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using JetBrains.Annotations;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -25,6 +24,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -38,7 +38,11 @@ namespace osu.Game.Screens.Select
private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0); 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] [Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; } private BeatmapDifficultyCache difficultyCache { get; set; }
@ -63,11 +67,10 @@ namespace osu.Game.Screens.Select
}; };
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader]
private void load([CanBeNull] Bindable<RulesetInfo> parentRuleset) private void load()
{ {
ruleset.BindTo(parentRuleset); ruleset.BindValueChanged(_ => updateDisplay());
ruleset.ValueChanged += _ => updateDisplay();
} }
protected override void PopIn() protected override void PopIn()
@ -132,7 +135,7 @@ namespace osu.Game.Screens.Select
return; return;
} }
LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value) LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value)
{ {
Shear = -Shear, Shear = -Shear,
Depth = Info?.Depth + 1 ?? 0 Depth = Info?.Depth + 1 ?? 0
@ -160,20 +163,25 @@ namespace osu.Game.Screens.Select
public OsuSpriteText ArtistLabel { get; private set; } public OsuSpriteText ArtistLabel { get; private set; }
public BeatmapSetOnlineStatusPill StatusPill { get; private set; } public BeatmapSetOnlineStatusPill StatusPill { get; private set; }
public FillFlowContainer MapperContainer { get; private set; } public FillFlowContainer MapperContainer { get; private set; }
public FillFlowContainer InfoLabelContainer { get; private set; }
private ILocalisedBindableString titleBinding; private ILocalisedBindableString titleBinding;
private ILocalisedBindableString artistBinding; private ILocalisedBindableString artistBinding;
private FillFlowContainer infoLabelContainer;
private Container bpmLabelContainer;
private readonly WorkingBeatmap beatmap; private readonly WorkingBeatmap beatmap;
private readonly RulesetInfo ruleset; private readonly RulesetInfo ruleset;
private readonly IReadOnlyList<Mod> mods;
private readonly StarDifficulty starDifficulty; 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) : base(pixelSnapping: true)
{ {
this.beatmap = beatmap; this.beatmap = beatmap;
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
this.mods = mods;
starDifficulty = difficulty; starDifficulty = difficulty;
} }
@ -184,7 +192,6 @@ namespace osu.Game.Screens.Select
var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
CacheDrawnFrameBuffer = true; CacheDrawnFrameBuffer = true;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title));
@ -302,12 +309,11 @@ namespace osu.Game.Screens.Select
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Children = getMapper(metadata) Children = getMapper(metadata)
}, },
InfoLabelContainer = new FillFlowContainer infoLabelContainer = new FillFlowContainer
{ {
Margin = new MarginPadding { Top = 20 }, Margin = new MarginPadding { Top = 20 },
Spacing = new Vector2(20, 0), Spacing = new Vector2(20, 0),
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Children = getInfoLabels()
} }
} }
} }
@ -319,6 +325,8 @@ namespace osu.Game.Screens.Select
// no difficulty means it can't have a status to show // no difficulty means it can't have a status to show
if (beatmapInfo.Version == null) if (beatmapInfo.Version == null)
StatusPill.Hide(); StatusPill.Hide();
addInfoLabels();
} }
private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0 private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0
@ -335,63 +343,91 @@ namespace osu.Game.Screens.Select
ForceRedraw(); ForceRedraw();
} }
private InfoLabel[] getInfoLabels() private void addInfoLabels()
{ {
var b = beatmap.Beatmap; if (beatmap.Beatmap?.HitObjects?.Any() != true)
return;
List<InfoLabel> labels = new List<InfoLabel>(); infoLabelContainer.Children = new Drawable[]
if (b?.HitObjects?.Any() == true)
{ {
labels.Add(new InfoLabel(new BeatmapStatistic new InfoLabel(new BeatmapStatistic
{ {
Name = "Length", Name = "Length",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
Content = TimeSpan.FromMilliseconds(b.BeatmapInfo.Length).ToString(@"m\:ss"), Content = TimeSpan.FromMilliseconds(beatmap.BeatmapInfo.Length).ToString(@"m\:ss"),
})); }),
bpmLabelContainer = new Container
labels.Add(new InfoLabel(new BeatmapStatistic
{ {
Name = "BPM", AutoSizeAxes = Axes.Both,
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm), },
Content = getBPMRange(b), new FillFlowContainer
})); {
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(20, 0),
Children = getRulesetInfoLabels()
}
};
settingChangeTracker = new ModSettingChangeTracker(mods);
settingChangeTracker.SettingChanged += _ => refreshBPMLabel();
refreshBPMLabel();
}
private InfoLabel[] getRulesetInfoLabels()
{
try
{
IBeatmap playableBeatmap;
try try
{ {
IBeatmap playableBeatmap; // Try to get the beatmap with the user's ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty<Mod>());
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>());
}
labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)));
} }
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; var b = beatmap.Beatmap;
double bpmMin = beatmap.ControlPointInfo.BPMMinimum; if (b == null)
return;
if (Precision.AlmostEquals(bpmMin, bpmMax)) // this doesn't consider mods which apply variable rates, yet.
return $"{bpmMin:0}"; double rate = 1;
foreach (var mod in mods.OfType<IApplicableToRate>())
rate = mod.ApplyToRate(0, rate);
return $"{bpmMin:0}-{bpmMax:0} (mostly {60000 / beatmap.GetMostCommonBeatLength():0})"; double bpmMax = b.ControlPointInfo.BPMMaximum * rate;
double bpmMin = b.ControlPointInfo.BPMMinimum * rate;
double mostCommonBPM = 60000 / b.GetMostCommonBeatLength() * rate;
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
});
ForceRedraw();
} }
private OsuSpriteText[] getMapper(BeatmapMetadata metadata) private OsuSpriteText[] getMapper(BeatmapMetadata metadata)
@ -414,6 +450,12 @@ namespace osu.Game.Screens.Select
}; };
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
settingChangeTracker?.Dispose();
}
public class InfoLabel : Container, IHasTooltip public class InfoLabel : Container, IHasTooltip
{ {
public string TooltipText { get; } public string TooltipText { get; }