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

Merge branch 'master' into fix-intermittent-failure

This commit is contained in:
Salman Ahmed 2022-03-19 02:49:21 +03:00 committed by GitHub
commit 60390a916a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 269 additions and 72 deletions

View File

@ -133,6 +133,7 @@ namespace osu.Game.Tests.Resources
StarRating = diff, StarRating = diff,
Length = length, Length = length,
BPM = bpm, BPM = bpm,
MaxCombo = 1000,
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = rulesetInfo, Ruleset = rulesetInfo,
Metadata = metadata, Metadata = metadata,

View File

@ -122,19 +122,33 @@ namespace osu.Game.Rulesets.Scoring
public static class HitResultExtensions public static class HitResultExtensions
{ {
/// <summary> /// <summary>
/// Whether a <see cref="HitResult"/> increases/decreases the combo, and affects the combo portion of the score. /// Whether a <see cref="HitResult"/> increases the combo.
/// </summary> /// </summary>
public static bool AffectsCombo(this HitResult result) public static bool IncreasesCombo(this HitResult result)
{ {
switch (result) switch (result)
{ {
case HitResult.Miss:
case HitResult.Meh: case HitResult.Meh:
case HitResult.Ok: case HitResult.Ok:
case HitResult.Good: case HitResult.Good:
case HitResult.Great: case HitResult.Great:
case HitResult.Perfect: case HitResult.Perfect:
case HitResult.LargeTickHit: case HitResult.LargeTickHit:
return true;
default:
return false;
}
}
/// <summary>
/// Whether a <see cref="HitResult"/> breaks the combo and resets it back to zero.
/// </summary>
public static bool BreaksCombo(this HitResult result)
{
switch (result)
{
case HitResult.Miss:
case HitResult.LargeTickMiss: case HitResult.LargeTickMiss:
return true; return true;
@ -143,6 +157,12 @@ namespace osu.Game.Rulesets.Scoring
} }
} }
/// <summary>
/// Whether a <see cref="HitResult"/> increases/breaks the combo, and affects the combo portion of the score.
/// </summary>
public static bool AffectsCombo(this HitResult result)
=> IncreasesCombo(result) || BreaksCombo(result);
/// <summary> /// <summary>
/// Whether a <see cref="HitResult"/> affects the accuracy portion of the score. /// Whether a <see cref="HitResult"/> affects the accuracy portion of the score.
/// </summary> /// </summary>

View File

@ -166,20 +166,10 @@ namespace osu.Game.Rulesets.Scoring
if (!result.Type.IsScorable()) if (!result.Type.IsScorable())
return; return;
if (result.Type.AffectsCombo()) if (result.Type.IncreasesCombo())
{
switch (result.Type)
{
case HitResult.Miss:
case HitResult.LargeTickMiss:
Combo.Value = 0;
break;
default:
Combo.Value++; Combo.Value++;
break; else if (result.Type.BreaksCombo())
} Combo.Value = 0;
}
double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0;

View File

@ -4,12 +4,16 @@
using System; using System;
using System.Linq; using System.Linq;
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.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; 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.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osuTK; using osuTK;
@ -19,11 +23,27 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
public class BarHitErrorMeter : HitErrorMeter public class BarHitErrorMeter : HitErrorMeter
{ {
private const int judgement_line_width = 14; 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 arrow;
private SpriteIcon iconEarly; private Drawable labelEarly;
private SpriteIcon iconLate; private Drawable labelLate;
private Container colourBarsEarly; private Container colourBarsEarly;
private Container colourBarsLate; private Container colourBarsLate;
@ -32,6 +52,18 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
private double maxHitWindow; 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() public BarHitErrorMeter()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
@ -40,13 +72,11 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
const int centre_marker_size = 8;
const int bar_height = 200; const int bar_height = 200;
const int bar_width = 2; const int bar_width = 2;
const float chevron_size = 8; const float chevron_size = 8;
const float icon_size = 14;
var hitWindows = HitWindows.GetAllAvailableWindows().ToArray(); hitWindows = HitWindows.GetAllAvailableWindows().ToArray();
InternalChild = new Container InternalChild = new Container
{ {
@ -65,22 +95,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Children = new Drawable[] 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 colourBarsEarly = new Container
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -98,14 +112,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Height = 0.5f, 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 judgementsContainer = new Container
{ {
Name = "judgements", Name = "judgements",
@ -114,24 +120,18 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = judgement_line_width, 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", Name = "average chevron",
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreRight,
Width = chevron_size, Width = chevron_size,
X = chevron_size,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Alpha = 0,
Scale = new Vector2(0, 1),
Child = arrow = new SpriteIcon Child = arrow = new SpriteIcon
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
@ -155,8 +155,180 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
colourBars.Height = 0; colourBars.Height = 0;
colourBars.ResizeHeightTo(1, 800, Easing.OutQuint); colourBars.ResizeHeightTo(1, 800, Easing.OutQuint);
arrow.Alpha = 0; CentreMarkerStyle.BindValueChanged(style => recreateCentreMarker(style.NewValue), true);
arrow.Delay(200).FadeInFromZero(600); 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() protected override void Update()
@ -164,8 +336,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
base.Update(); base.Update();
// undo any layout rotation to display icons in the correct orientation // undo any layout rotation to display icons in the correct orientation
iconEarly.Rotation = -Rotation; if (labelEarly != null) labelEarly.Rotation = -Rotation;
iconLate.Rotation = -Rotation; if (labelLate != null) labelLate.Rotation = -Rotation;
} }
private void createColourBars((HitResult result, double length)[] windows) 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) protected override void OnNewJudgement(JudgementResult judgement)
{ {
const int arrow_move_duration = 800; const int arrow_move_duration = 800;
@ -255,6 +422,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
judgementsContainer.Add(new JudgementLine judgementsContainer.Add(new JudgementLine
{ {
JudgementLineThickness = { BindTarget = JudgementLineThickness },
Y = getRelativeJudgementPosition(judgement.TimeOffset), Y = getRelativeJudgementPosition(judgement.TimeOffset),
Colour = GetColourForHitResult(judgement.Type), Colour = GetColourForHitResult(judgement.Type),
}); });
@ -268,11 +436,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
internal class JudgementLine : CompositeDrawable internal class JudgementLine : CompositeDrawable
{ {
public readonly BindableNumber<float> JudgementLineThickness = new BindableFloat();
public JudgementLine() public JudgementLine()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
RelativePositionAxes = Axes.Y; RelativePositionAxes = Axes.Y;
Height = judgement_line_height;
Blending = BlendingParameters.Additive; Blending = BlendingParameters.Additive;
@ -295,6 +464,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
Alpha = 0; Alpha = 0;
Width = 0; Width = 0;
JudgementLineThickness.BindValueChanged(thickness => Height = thickness.NewValue, true);
this this
.FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint) .FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint)
.ResizeWidthTo(1, 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 override void Clear() => judgementsContainer.Clear();
public enum CentreMarkerStyles
{
None,
Circle,
Line
}
public enum LabelStyles
{
None,
Icons,
Text
}
} }
} }

View File

@ -68,7 +68,7 @@ namespace osu.Game.Screens.Ranking.Expanded
var topStatistics = new List<StatisticDisplay> var topStatistics = new List<StatisticDisplay>
{ {
new AccuracyStatistic(score.Accuracy), new AccuracyStatistic(score.Accuracy),
new ComboStatistic(score.MaxCombo, !score.Statistics.TryGetValue(HitResult.Miss, out int missCount) || missCount == 0), new ComboStatistic(score.MaxCombo, beatmap.MaxCombo, score.Statistics.All(stat => !stat.Key.BreaksCombo() || stat.Value == 0)),
new PerformanceStatistic(score), new PerformanceStatistic(score),
}; };

View File

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

View File

@ -51,7 +51,7 @@ namespace osu.Game.Skinning.Editor
fill.Clear(); fill.Clear();
var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() var skinnableTypes = typeof(OsuGame).Assembly.GetTypes()
.Where(t => !t.IsInterface) .Where(t => !t.IsInterface && !t.IsAbstract)
.Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t))
.OrderBy(t => t.Name) .OrderBy(t => t.Name)
.ToArray(); .ToArray();