Merge branch 'master' into screen-mod-retention

This commit is contained in:
Dean Herbert
2022-03-22 17:27:26 +09:00
committed by GitHub
49 changed files with 1325 additions and 239 deletions

View File

@ -7,7 +7,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
@ -25,6 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
/// </summary>
public Action<PlaylistItem> RequestEdit;
private MultiplayerPlaylistTabControl playlistTabControl;
private MultiplayerQueueList queueList;
private MultiplayerHistoryList historyList;
private bool firstPopulation = true;
@ -36,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
InternalChildren = new Drawable[]
{
new OsuTabControl<MultiplayerPlaylistDisplayMode>
playlistTabControl = new MultiplayerPlaylistTabControl
{
RelativeSizeAxes = Axes.X,
Height = tab_control_height,
@ -64,6 +64,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
}
}
};
playlistTabControl.QueueItems.BindTarget = queueList.Items;
}
protected override void LoadComplete()

View File

@ -0,0 +1,39 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
{
public class MultiplayerPlaylistTabControl : OsuTabControl<MultiplayerPlaylistDisplayMode>
{
public readonly IBindableList<PlaylistItem> QueueItems = new BindableList<PlaylistItem>();
protected override TabItem<MultiplayerPlaylistDisplayMode> CreateTabItem(MultiplayerPlaylistDisplayMode value)
{
if (value == MultiplayerPlaylistDisplayMode.Queue)
return new QueueTabItem { QueueItems = { BindTarget = QueueItems } };
return base.CreateTabItem(value);
}
private class QueueTabItem : OsuTabItem
{
public readonly IBindableList<PlaylistItem> QueueItems = new BindableList<PlaylistItem>();
public QueueTabItem()
: base(MultiplayerPlaylistDisplayMode.Queue)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
QueueItems.BindCollectionChanged((_, __) => Text.Text = QueueItems.Count > 0 ? $"Queue ({QueueItems.Count})" : "Queue", true);
}
}
}
}

View File

@ -4,12 +4,16 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osuTK;
@ -19,11 +23,27 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
public class BarHitErrorMeter : HitErrorMeter
{
private const int judgement_line_width = 14;
private const int judgement_line_height = 4;
[SettingSource("Judgement line thickness", "How thick the individual lines should be.")]
public BindableNumber<float> JudgementLineThickness { get; } = new BindableNumber<float>(4)
{
MinValue = 1,
MaxValue = 8,
Precision = 0.1f,
};
[SettingSource("Show moving average arrow", "Whether an arrow should move beneath the bar showing the average error.")]
public Bindable<bool> ShowMovingAverage { get; } = new BindableBool(true);
[SettingSource("Centre marker style", "How to signify the centre of the display")]
public Bindable<CentreMarkerStyles> CentreMarkerStyle { get; } = new Bindable<CentreMarkerStyles>(CentreMarkerStyles.Circle);
[SettingSource("Label style", "How to show early/late extremities")]
public Bindable<LabelStyles> LabelStyle { get; } = new Bindable<LabelStyles>(LabelStyles.Icons);
private SpriteIcon arrow;
private SpriteIcon iconEarly;
private SpriteIcon iconLate;
private Drawable labelEarly;
private Drawable labelLate;
private Container colourBarsEarly;
private Container colourBarsLate;
@ -32,6 +52,18 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
private double maxHitWindow;
private double floatingAverage;
private Container colourBars;
private Container arrowContainer;
private (HitResult result, double length)[] hitWindows;
private const int max_concurrent_judgements = 50;
private Drawable[] centreMarkerDrawables;
private const int centre_marker_size = 8;
public BarHitErrorMeter()
{
AutoSizeAxes = Axes.Both;
@ -40,13 +72,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
[BackgroundDependencyLoader]
private void load()
{
const int centre_marker_size = 8;
const int bar_height = 200;
const int bar_width = 2;
const float chevron_size = 8;
const float icon_size = 14;
var hitWindows = HitWindows.GetAllAvailableWindows().ToArray();
hitWindows = HitWindows.GetAllAvailableWindows().ToArray();
InternalChild = new Container
{
@ -65,22 +95,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
iconEarly = new SpriteIcon
{
Y = -10,
Size = new Vector2(icon_size),
Icon = FontAwesome.Solid.ShippingFast,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
},
iconLate = new SpriteIcon
{
Y = 10,
Size = new Vector2(icon_size),
Icon = FontAwesome.Solid.Bicycle,
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
},
colourBarsEarly = new Container
{
Anchor = Anchor.Centre,
@ -98,14 +112,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
},
new Circle
{
Name = "middle marker behind",
Colour = GetColourForHitResult(hitWindows.Last().result),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(centre_marker_size),
},
judgementsContainer = new Container
{
Name = "judgements",
@ -114,24 +120,18 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
RelativeSizeAxes = Axes.Y,
Width = judgement_line_width,
},
new Circle
{
Name = "middle marker in front",
Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(centre_marker_size),
Scale = new Vector2(0.5f),
},
}
},
new Container
arrowContainer = new Container
{
Name = "average chevron",
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Origin = Anchor.CentreRight,
Width = chevron_size,
X = chevron_size,
RelativeSizeAxes = Axes.Y,
Alpha = 0,
Scale = new Vector2(0, 1),
Child = arrow = new SpriteIcon
{
Anchor = Anchor.TopCentre,
@ -155,8 +155,180 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
colourBars.Height = 0;
colourBars.ResizeHeightTo(1, 800, Easing.OutQuint);
arrow.Alpha = 0;
arrow.Delay(200).FadeInFromZero(600);
CentreMarkerStyle.BindValueChanged(style => recreateCentreMarker(style.NewValue), true);
LabelStyle.BindValueChanged(style => recreateLabels(style.NewValue), true);
// delay the appearance animations for only the initial appearance.
using (arrowContainer.BeginDelayedSequence(450))
{
ShowMovingAverage.BindValueChanged(visible =>
{
arrowContainer.FadeTo(visible.NewValue ? 1 : 0, 250, Easing.OutQuint);
arrowContainer.ScaleTo(visible.NewValue ? new Vector2(1) : new Vector2(0, 1), 250, Easing.OutQuint);
}, true);
}
}
private void recreateCentreMarker(CentreMarkerStyles style)
{
if (centreMarkerDrawables != null)
{
foreach (var d in centreMarkerDrawables)
{
d.ScaleTo(0, 500, Easing.OutQuint)
.FadeOut(500, Easing.OutQuint);
d.Expire();
}
centreMarkerDrawables = null;
}
switch (style)
{
case CentreMarkerStyles.None:
break;
case CentreMarkerStyles.Circle:
centreMarkerDrawables = new Drawable[]
{
new Circle
{
Name = "middle marker behind",
Colour = GetColourForHitResult(hitWindows.Last().result),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Depth = float.MaxValue,
Size = new Vector2(centre_marker_size),
},
new Circle
{
Name = "middle marker in front",
Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Depth = float.MinValue,
Size = new Vector2(centre_marker_size / 2f),
},
};
break;
case CentreMarkerStyles.Line:
const float border_size = 1.5f;
centreMarkerDrawables = new Drawable[]
{
new Box
{
Name = "middle marker behind",
Colour = GetColourForHitResult(hitWindows.Last().result),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Depth = float.MaxValue,
Size = new Vector2(judgement_line_width, centre_marker_size / 3f),
},
new Box
{
Name = "middle marker in front",
Colour = GetColourForHitResult(hitWindows.Last().result).Darken(0.3f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Depth = float.MinValue,
Size = new Vector2(judgement_line_width - border_size, centre_marker_size / 3f - border_size),
},
};
break;
default:
throw new ArgumentOutOfRangeException(nameof(style), style, null);
}
if (centreMarkerDrawables != null)
{
foreach (var d in centreMarkerDrawables)
{
colourBars.Add(d);
d.FadeInFromZero(500, Easing.OutQuint)
.ScaleTo(0).ScaleTo(1, 1000, Easing.OutElasticHalf);
}
}
}
private void recreateLabels(LabelStyles style)
{
const float icon_size = 14;
labelEarly?.Expire();
labelEarly = null;
labelLate?.Expire();
labelLate = null;
switch (style)
{
case LabelStyles.None:
break;
case LabelStyles.Icons:
labelEarly = new SpriteIcon
{
Y = -10,
Size = new Vector2(icon_size),
Icon = FontAwesome.Solid.ShippingFast,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
};
labelLate = new SpriteIcon
{
Y = 10,
Size = new Vector2(icon_size),
Icon = FontAwesome.Solid.Bicycle,
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
};
break;
case LabelStyles.Text:
labelEarly = new OsuSpriteText
{
Y = -10,
Text = "Early",
Font = OsuFont.Default.With(size: 10),
Height = 12,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
};
labelLate = new OsuSpriteText
{
Y = 10,
Text = "Late",
Font = OsuFont.Default.With(size: 10),
Height = 12,
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
};
break;
default:
throw new ArgumentOutOfRangeException(nameof(style), style, null);
}
if (labelEarly != null)
{
colourBars.Add(labelEarly);
labelEarly.FadeInFromZero(500);
}
if (labelLate != null)
{
colourBars.Add(labelLate);
labelLate.FadeInFromZero(500);
}
}
protected override void Update()
@ -164,8 +336,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
base.Update();
// undo any layout rotation to display icons in the correct orientation
iconEarly.Rotation = -Rotation;
iconLate.Rotation = -Rotation;
if (labelEarly != null) labelEarly.Rotation = -Rotation;
if (labelLate != null) labelLate.Rotation = -Rotation;
}
private void createColourBars((HitResult result, double length)[] windows)
@ -224,11 +396,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
}
}
private double floatingAverage;
private Container colourBars;
private const int max_concurrent_judgements = 50;
protected override void OnNewJudgement(JudgementResult judgement)
{
const int arrow_move_duration = 800;
@ -255,6 +422,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
judgementsContainer.Add(new JudgementLine
{
JudgementLineThickness = { BindTarget = JudgementLineThickness },
Y = getRelativeJudgementPosition(judgement.TimeOffset),
Colour = GetColourForHitResult(judgement.Type),
});
@ -268,11 +436,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
internal class JudgementLine : CompositeDrawable
{
public readonly BindableNumber<float> JudgementLineThickness = new BindableFloat();
public JudgementLine()
{
RelativeSizeAxes = Axes.X;
RelativePositionAxes = Axes.Y;
Height = judgement_line_height;
Blending = BlendingParameters.Additive;
@ -295,6 +464,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
Alpha = 0;
Width = 0;
JudgementLineThickness.BindValueChanged(thickness => Height = thickness.NewValue, true);
this
.FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint)
.ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint)
@ -306,5 +477,19 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
}
public override void Clear() => judgementsContainer.Clear();
public enum CentreMarkerStyles
{
None,
Circle,
Line
}
public enum LabelStyles
{
None,
Icons,
Text
}
}
}

View File

@ -84,7 +84,7 @@ namespace osu.Game.Screens.Play.HUD
protected override bool OnMouseMove(MouseMoveEvent e)
{
positionalAdjust = Vector2.Distance(e.ScreenSpaceMousePosition, button.ScreenSpaceDrawQuad.Centre) / 200;
positionalAdjust = Vector2.Distance(e.MousePosition, button.ToSpaceOfOtherDrawable(button.DrawRectangle.Centre, Parent)) / 100;
return base.OnMouseMove(e);
}

View File

@ -42,12 +42,10 @@ namespace osu.Game.Screens.Play.HUD
private const float alpha_when_invalid = 0.3f;
[CanBeNull]
[Resolved(CanBeNull = true)]
[Resolved]
private ScoreProcessor scoreProcessor { get; set; }
[Resolved(CanBeNull = true)]
[CanBeNull]
[Resolved]
private GameplayState gameplayState { get; set; }
[CanBeNull]

View File

@ -615,16 +615,22 @@ namespace osu.Game.Screens.Play
/// <param name="time">The destination time to seek to.</param>
internal void NonFrameStableSeek(double time)
{
if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed)
frameStablePlaybackResetDelegate.RunTask();
// TODO: This schedule should not be required and is a temporary hotfix.
// See https://github.com/ppy/osu/issues/17267 for the issue.
// See https://github.com/ppy/osu/pull/17302 for a better fix which needs some more time.
ScheduleAfterChildren(() =>
{
if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed)
frameStablePlaybackResetDelegate.RunTask();
bool wasFrameStable = DrawableRuleset.FrameStablePlayback;
DrawableRuleset.FrameStablePlayback = false;
bool wasFrameStable = DrawableRuleset.FrameStablePlayback;
DrawableRuleset.FrameStablePlayback = false;
Seek(time);
Seek(time);
// Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek.
frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable);
// Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek.
frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable);
});
}
/// <summary>

View File

@ -1,10 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable enable
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using JetBrains.Annotations;
using ManagedBass.Fx;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -48,31 +49,31 @@ namespace osu.Game.Screens.Play
public override bool HandlePositionalInput => true;
// We show the previous screen status
protected override UserActivity InitialActivity => null;
protected override UserActivity? InitialActivity => null;
protected override bool PlayResumeSound => false;
protected BeatmapMetadataDisplay MetadataInfo { get; private set; }
protected BeatmapMetadataDisplay MetadataInfo { get; private set; } = null!;
/// <summary>
/// A fill flow containing the player settings groups, exposed for the ability to hide it from inheritors of the player loader.
/// </summary>
protected FillFlowContainer<PlayerSettingsGroup> PlayerSettings { get; private set; }
protected FillFlowContainer<PlayerSettingsGroup> PlayerSettings { get; private set; } = null!;
protected VisualSettings VisualSettings { get; private set; }
protected VisualSettings VisualSettings { get; private set; } = null!;
protected AudioSettings AudioSettings { get; private set; }
protected AudioSettings AudioSettings { get; private set; } = null!;
protected Task LoadTask { get; private set; }
protected Task? LoadTask { get; private set; }
protected Task DisposalTask { get; private set; }
protected Task? DisposalTask { get; private set; }
private bool backgroundBrightnessReduction;
private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
private AudioFilter lowPassFilter;
private AudioFilter highPassFilter;
private AudioFilter lowPassFilter = null!;
private AudioFilter highPassFilter = null!;
protected bool BackgroundBrightnessReduction
{
@ -90,47 +91,49 @@ namespace osu.Game.Screens.Play
private bool readyForPush =>
!playerConsumed
// don't push unless the player is completely loaded
&& player?.LoadState == LoadState.Ready
&& CurrentPlayer?.LoadState == LoadState.Ready
// don't push if the user is hovering one of the panes, unless they are idle.
&& (IsHovered || idleTracker.IsIdle.Value)
// don't push if the user is dragging a slider or otherwise.
&& inputManager?.DraggedDrawable == null
&& inputManager.DraggedDrawable == null
// don't push if a focused overlay is visible, like settings.
&& inputManager?.FocusedDrawable == null;
&& inputManager.FocusedDrawable == null;
private readonly Func<Player> createPlayer;
private Player player;
/// <summary>
/// The <see cref="Player"/> instance being loaded by this screen.
/// </summary>
public Player? CurrentPlayer { get; private set; }
/// <summary>
/// Whether the curent player instance has been consumed via <see cref="consumePlayer"/>.
/// Whether the current player instance has been consumed via <see cref="consumePlayer"/>.
/// </summary>
private bool playerConsumed;
private LogoTrackingContainer content;
private LogoTrackingContainer content = null!;
private bool hideOverlays;
private InputManager inputManager;
private InputManager inputManager = null!;
private IdleTracker idleTracker;
private IdleTracker idleTracker = null!;
private ScheduledDelegate scheduledPushPlayer;
private ScheduledDelegate? scheduledPushPlayer;
[CanBeNull]
private EpilepsyWarning epilepsyWarning;
private EpilepsyWarning? epilepsyWarning;
[Resolved(CanBeNull = true)]
private NotificationOverlay notificationOverlay { get; set; }
private NotificationOverlay? notificationOverlay { get; set; }
[Resolved(CanBeNull = true)]
private VolumeOverlay volumeOverlay { get; set; }
private VolumeOverlay? volumeOverlay { get; set; }
[Resolved]
private AudioManager audioManager { get; set; }
private AudioManager audioManager { get; set; } = null!;
[Resolved(CanBeNull = true)]
private BatteryInfo batteryInfo { get; set; }
private BatteryInfo? batteryInfo { get; set; }
public PlayerLoader(Func<Player> createPlayer)
{
@ -237,12 +240,14 @@ namespace osu.Game.Screens.Play
{
base.OnResuming(last);
var lastScore = player.Score;
Debug.Assert(CurrentPlayer != null);
var lastScore = CurrentPlayer.Score;
AudioSettings.ReferenceScore.Value = lastScore?.ScoreInfo;
// prepare for a retry.
player = null;
CurrentPlayer = null;
playerConsumed = false;
cancelLoad();
@ -344,9 +349,10 @@ namespace osu.Game.Screens.Play
private Player consumePlayer()
{
Debug.Assert(!playerConsumed);
Debug.Assert(CurrentPlayer != null);
playerConsumed = true;
return player;
return CurrentPlayer;
}
private void prepareNewPlayer()
@ -354,11 +360,11 @@ namespace osu.Game.Screens.Play
if (!this.IsCurrentScreen())
return;
player = createPlayer();
player.RestartCount = restartCount++;
player.RestartRequested = restartRequested;
CurrentPlayer = createPlayer();
CurrentPlayer.RestartCount = restartCount++;
CurrentPlayer.RestartRequested = restartRequested;
LoadTask = LoadComponentAsync(player, _ => MetadataInfo.Loading = false);
LoadTask = LoadComponentAsync(CurrentPlayer, _ => MetadataInfo.Loading = false);
}
private void restartRequested()
@ -472,7 +478,7 @@ namespace osu.Game.Screens.Play
if (isDisposing)
{
// if the player never got pushed, we should explicitly dispose it.
DisposalTask = LoadTask?.ContinueWith(_ => player?.Dispose());
DisposalTask = LoadTask?.ContinueWith(_ => CurrentPlayer?.Dispose());
}
}
@ -480,7 +486,7 @@ namespace osu.Game.Screens.Play
#region Mute warning
private Bindable<bool> muteWarningShownOnce;
private Bindable<bool> muteWarningShownOnce = null!;
private int restartCount;
@ -535,7 +541,7 @@ namespace osu.Game.Screens.Play
#region Low battery warning
private Bindable<bool> batteryWarningShownOnce;
private Bindable<bool> batteryWarningShownOnce = null!;
private void showBatteryWarningIfNeeded()
{

View File

@ -73,9 +73,12 @@ namespace osu.Game.Screens.Play
[Resolved(canBeNull: true)]
private Player player { get; set; }
[Resolved(canBeNull: true)]
[Resolved]
private GameplayClock gameplayClock { get; set; }
[Resolved(canBeNull: true)]
private DrawableRuleset drawableRuleset { get; set; }
private IClock referenceClock;
public bool UsesFixedAnchor { get; set; }
@ -113,7 +116,7 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset)
private void load(OsuColour colours, OsuConfigManager config)
{
base.LoadComplete();

View File

@ -65,10 +65,12 @@ namespace osu.Game.Screens.Ranking.Expanded
var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata;
string creator = metadata.Author.Username;
int? beatmapMaxCombo = scoreManager.GetMaximumAchievableComboAsync(score).GetResultSafely();
var topStatistics = new List<StatisticDisplay>
{
new AccuracyStatistic(score.Accuracy),
new ComboStatistic(score.MaxCombo, !score.Statistics.TryGetValue(HitResult.Miss, out int missCount) || missCount == 0),
new ComboStatistic(score.MaxCombo, beatmapMaxCombo),
new PerformanceStatistic(score),
};
@ -80,8 +82,6 @@ namespace osu.Game.Screens.Ranking.Expanded
statisticDisplays.AddRange(topStatistics);
statisticDisplays.AddRange(bottomStatistics);
var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely();
AddInternal(new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
@ -224,6 +224,8 @@ namespace osu.Game.Screens.Ranking.Expanded
if (score.Date != default)
AddInternal(new PlayedOnText(score.Date));
var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely();
if (starDifficulty != null)
{
starAndModDisplay.Add(new StarRatingDisplay(starDifficulty.Value)

View File

@ -25,11 +25,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
/// Creates a new <see cref="ComboStatistic"/>.
/// </summary>
/// <param name="combo">The combo to be displayed.</param>
/// <param name="isPerfect">Whether this is a perfect combo.</param>
public ComboStatistic(int combo, bool isPerfect)
: base("combo", combo)
/// <param name="maxCombo">The maximum value of <paramref name="combo"/>.</param>
public ComboStatistic(int combo, int? maxCombo)
: base("combo", combo, maxCombo)
{
this.isPerfect = isPerfect;
isPerfect = combo == maxCombo;
}
public override void Appear()