diff --git a/osu.Android.props b/osu.Android.props
index 66a1523843..942970c890 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index c1bd73ef05..c6095ae404 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -417,7 +417,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWithDuplicateHashes()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateHashes)))
{
try
{
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
new file mode 100644
index 0000000000..d0b9d43f51
--- /dev/null
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
@@ -0,0 +1,155 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+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.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Ranking.Expanded.Accuracy;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Ranking
+{
+ public class TestSceneAccuracyCircle : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(AccuracyCircle),
+ typeof(RankBadge),
+ typeof(RankNotch),
+ typeof(RankText),
+ typeof(SmoothCircularProgress)
+ };
+
+ [Test]
+ public void TestDRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.5;
+ score.Rank = ScoreRank.D;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestCRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.75;
+ score.Rank = ScoreRank.C;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestBRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.85;
+ score.Rank = ScoreRank.B;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestARank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.925;
+ score.Rank = ScoreRank.A;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestSRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.975;
+ score.Rank = ScoreRank.S;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestAlmostSSRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.9999;
+ score.Rank = ScoreRank.S;
+
+ addCircleStep(score);
+ }
+
+ [Test]
+ public void TestSSRank()
+ {
+ var score = createScore();
+ score.Accuracy = 1;
+ score.Rank = ScoreRank.X;
+
+ addCircleStep(score);
+ }
+
+ private void addCircleStep(ScoreInfo score) => AddStep("add panel", () =>
+ {
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(500, 700),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#555"), Color4Extensions.FromHex("#333"))
+ }
+ }
+ },
+ new AccuracyCircle(score)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(230)
+ }
+ };
+ });
+
+ private ScoreInfo createScore() => new ScoreInfo
+ {
+ User = new User
+ {
+ Id = 2,
+ Username = "peppy",
+ },
+ Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
+ Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
+ TotalScore = 2845370,
+ Accuracy = 0.95,
+ MaxCombo = 999,
+ Rank = ScoreRank.S,
+ Date = DateTimeOffset.Now,
+ Statistics =
+ {
+ { HitResult.Miss, 1 },
+ { HitResult.Meh, 50 },
+ { HitResult.Good, 100 },
+ { HitResult.Great, 300 },
+ }
+ };
+ }
+}
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneRoundDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneRoundDisplay.cs
new file mode 100644
index 0000000000..6f71627ce4
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Components/TestSceneRoundDisplay.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
+
+namespace osu.Game.Tournament.Tests.Components
+{
+ public class TestSceneRoundDisplay : TournamentTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableTournamentHeaderText),
+ typeof(DrawableTournamentHeaderLogo),
+ };
+
+ public TestSceneRoundDisplay()
+ {
+ Children = new Drawable[]
+ {
+ new RoundDisplay(new TournamentMatch
+ {
+ Round =
+ {
+ Value = new TournamentRound
+ {
+ Name = { Value = "Test Round" }
+ }
+ }
+ })
+ {
+ Margin = new MarginPadding(20)
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tournament/Components/DrawableTournamentHeaderText.cs b/osu.Game.Tournament/Components/DrawableTournamentHeaderText.cs
index bda696ba00..99d914fed4 100644
--- a/osu.Game.Tournament/Components/DrawableTournamentHeaderText.cs
+++ b/osu.Game.Tournament/Components/DrawableTournamentHeaderText.cs
@@ -11,9 +11,13 @@ namespace osu.Game.Tournament.Components
{
public class DrawableTournamentHeaderText : CompositeDrawable
{
- public DrawableTournamentHeaderText()
+ public DrawableTournamentHeaderText(bool center = true)
{
- InternalChild = new TextSprite();
+ InternalChild = new TextSprite
+ {
+ Anchor = center ? Anchor.Centre : Anchor.TopLeft,
+ Origin = center ? Anchor.Centre : Anchor.TopLeft,
+ };
Height = 22;
RelativeSizeAxes = Axes.X;
@@ -27,9 +31,6 @@ namespace osu.Game.Tournament.Components
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
- Anchor = Anchor.Centre;
- Origin = Anchor.Centre;
-
Texture = textures.Get("header-text");
}
}
diff --git a/osu.Game.Tournament/Components/RoundDisplay.cs b/osu.Game.Tournament/Components/RoundDisplay.cs
index bebede6782..c0002e6804 100644
--- a/osu.Game.Tournament/Components/RoundDisplay.cs
+++ b/osu.Game.Tournament/Components/RoundDisplay.cs
@@ -12,19 +12,27 @@ namespace osu.Game.Tournament.Components
{
public RoundDisplay(TournamentMatch match)
{
- AutoSizeAxes = Axes.Both;
+ AutoSizeAxes = Axes.Y;
+ RelativeSizeAxes = Axes.X;
InternalChildren = new Drawable[]
{
new FillFlowContainer
{
- AutoSizeAxes = Axes.Both,
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- new DrawableTournamentHeaderText(),
+ new DrawableTournamentHeaderText(false)
+ {
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.TopLeft,
+ },
new TournamentSpriteText
{
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.TopLeft,
Text = match.Round.Value?.Name.Value ?? "Unknown Round",
Font = OsuFont.Torus.With(size: 26, weight: FontWeight.SemiBold)
},
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs
index 462015f004..3e60a03f92 100644
--- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs
@@ -35,7 +35,9 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
private void load(LadderInfo ladder)
{
currentMatch.BindTo(ladder.CurrentMatch);
- currentMatch.BindValueChanged(matchChanged, true);
+ currentMatch.BindValueChanged(matchChanged);
+
+ updateMatch();
}
private void matchChanged(ValueChangedEvent match)
@@ -43,10 +45,19 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
currentTeamScore.UnbindBindings();
currentTeam.UnbindBindings();
- if (match.NewValue != null)
+ Scheduler.AddOnce(updateMatch);
+ }
+
+ private void updateMatch()
+ {
+ var match = currentMatch.Value;
+
+ if (match != null)
{
- currentTeamScore.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1Score : match.NewValue.Team2Score);
- currentTeam.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1 : match.NewValue.Team2);
+ match.StartMatch();
+
+ currentTeamScore.BindTo(teamColour == TeamColour.Red ? match.Team1Score : match.Team2Score);
+ currentTeam.BindTo(teamColour == TeamColour.Red ? match.Team1 : match.Team2);
}
// team may change to same team, which means score is not in a good state.
diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs
index 984f5e52d1..f7ed55410c 100644
--- a/osu.Game/Graphics/OsuColour.cs
+++ b/osu.Game/Graphics/OsuColour.cs
@@ -3,6 +3,7 @@
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Beatmaps;
+using osu.Game.Scoring;
using osuTK.Graphics;
namespace osu.Game.Graphics
@@ -37,6 +38,35 @@ namespace osu.Game.Graphics
}
}
+ ///
+ /// Retrieves the colour for a .
+ ///
+ public static Color4 ForRank(ScoreRank rank)
+ {
+ switch (rank)
+ {
+ case ScoreRank.XH:
+ case ScoreRank.X:
+ return Color4Extensions.FromHex(@"ce1c9d");
+
+ case ScoreRank.SH:
+ case ScoreRank.S:
+ return Color4Extensions.FromHex(@"00a8b5");
+
+ case ScoreRank.A:
+ return Color4Extensions.FromHex(@"7cce14");
+
+ case ScoreRank.B:
+ return Color4Extensions.FromHex(@"e3b130");
+
+ case ScoreRank.C:
+ return Color4Extensions.FromHex(@"f18252");
+
+ default:
+ return Color4Extensions.FromHex(@"e95353");
+ }
+ }
+
// See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less
public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff");
public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff");
diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs
index 255f7f24f7..7c78141b4d 100644
--- a/osu.Game/Graphics/OsuFont.cs
+++ b/osu.Game/Graphics/OsuFont.cs
@@ -136,5 +136,10 @@ namespace osu.Game.Graphics
/// Equivalent to weight 700.
///
Bold = 700,
+
+ ///
+ /// Equivalent to weight 900.
+ ///
+ Black = 900
}
}
diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
index 12688da9df..4aea5aa518 100644
--- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
+++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
@@ -43,6 +43,18 @@ namespace osu.Game.Graphics.Sprites
set => blurredText.Colour = value;
}
+ public Vector2 Spacing
+ {
+ get => spriteText.Spacing;
+ set => spriteText.Spacing = blurredText.Spacing = value;
+ }
+
+ public bool UseFullGlyphHeight
+ {
+ get => spriteText.UseFullGlyphHeight;
+ set => spriteText.UseFullGlyphHeight = blurredText.UseFullGlyphHeight = value;
+ }
+
public GlowingSpriteText()
{
AutoSizeAxes = Axes.Both;
diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs
index 45b91bbf81..4d41230799 100644
--- a/osu.Game/Online/Leaderboards/DrawableRank.cs
+++ b/osu.Game/Online/Leaderboards/DrawableRank.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Online.Leaderboards
FillMode = FillMode.Fit;
FillAspectRatio = 2;
- var rankColour = getRankColour();
+ var rankColour = OsuColour.ForRank(rank);
InternalChild = new DrawSizePreservingFillContainer
{
TargetDrawSize = new Vector2(64, 32),
@@ -59,7 +59,7 @@ namespace osu.Game.Online.Leaderboards
Padding = new MarginPadding { Top = 5 },
Colour = getRankNameColour(),
Font = OsuFont.Numeric.With(size: 25),
- Text = getRankName(),
+ Text = GetRankName(rank),
ShadowColour = Color4.Black.Opacity(0.3f),
ShadowOffset = new Vector2(0, 0.08f),
Shadow = true,
@@ -69,36 +69,7 @@ namespace osu.Game.Online.Leaderboards
};
}
- private string getRankName() => rank.GetDescription().TrimEnd('+');
-
- ///
- /// Retrieves the grade background colour.
- ///
- private Color4 getRankColour()
- {
- switch (rank)
- {
- case ScoreRank.XH:
- case ScoreRank.X:
- return Color4Extensions.FromHex(@"ce1c9d");
-
- case ScoreRank.SH:
- case ScoreRank.S:
- return Color4Extensions.FromHex(@"00a8b5");
-
- case ScoreRank.A:
- return Color4Extensions.FromHex(@"7cce14");
-
- case ScoreRank.B:
- return Color4Extensions.FromHex(@"e3b130");
-
- case ScoreRank.C:
- return Color4Extensions.FromHex(@"f18252");
-
- default:
- return Color4Extensions.FromHex(@"e95353");
- }
- }
+ public static string GetRankName(ScoreRank rank) => rank.GetDescription().TrimEnd('+');
///
/// Retrieves the grade text colour.
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index bcadba14af..79f92c3762 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -401,14 +401,18 @@ namespace osu.Game.Screens.Play
protected virtual ScoreInfo CreateScore()
{
- var score = DrawableRuleset.ReplayScore?.ScoreInfo ?? new ScoreInfo
+ var score = new ScoreInfo
{
Beatmap = Beatmap.Value.BeatmapInfo,
Ruleset = rulesetInfo,
Mods = Mods.Value.ToArray(),
- User = api.LocalUser.Value,
};
+ if (DrawableRuleset.ReplayScore != null)
+ score.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser();
+ else
+ score.User = api.LocalUser.Value;
+
ScoreProcessor.PopulateScore(score);
return score;
diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs
new file mode 100644
index 0000000000..4b6f777283
--- /dev/null
+++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs
@@ -0,0 +1,253 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Utils;
+using osu.Game.Graphics;
+using osu.Game.Scoring;
+using osuTK;
+
+namespace osu.Game.Screens.Ranking.Expanded.Accuracy
+{
+ ///
+ /// The component that displays the player's accuracy on the results screen.
+ ///
+ public class AccuracyCircle : CompositeDrawable
+ {
+ ///
+ /// Duration for the transforms causing this component to appear.
+ ///
+ public const double APPEAR_DURATION = 200;
+
+ ///
+ /// Delay before the accuracy circle starts filling.
+ ///
+ public const double ACCURACY_TRANSFORM_DELAY = 450;
+
+ ///
+ /// Duration for the accuracy circle fill.
+ ///
+ public const double ACCURACY_TRANSFORM_DURATION = 3000;
+
+ ///
+ /// Delay after for the rank text (A/B/C/D/S/SS) to appear.
+ ///
+ public const double TEXT_APPEAR_DELAY = ACCURACY_TRANSFORM_DURATION / 2;
+
+ ///
+ /// Delay before the rank circles start filling.
+ ///
+ public const double RANK_CIRCLE_TRANSFORM_DELAY = 150;
+
+ ///
+ /// Duration for the rank circle fills.
+ ///
+ public const double RANK_CIRCLE_TRANSFORM_DURATION = 800;
+
+ ///
+ /// Relative width of the rank circles.
+ ///
+ public const float RANK_CIRCLE_RADIUS = 0.06f;
+
+ ///
+ /// Relative width of the circle showing the accuracy.
+ ///
+ private const float accuracy_circle_radius = 0.2f;
+
+ ///
+ /// SS is displayed as a 1% region, otherwise it would be invisible.
+ ///
+ private const double virtual_ss_percentage = 0.01;
+
+ ///
+ /// The easing for the circle filling transforms.
+ ///
+ public static readonly Easing ACCURACY_TRANSFORM_EASING = Easing.OutPow10;
+
+ private readonly ScoreInfo score;
+
+ private SmoothCircularProgress accuracyCircle;
+ private SmoothCircularProgress innerMask;
+ private Container badges;
+ private RankText rankText;
+
+ public AccuracyCircle(ScoreInfo score)
+ {
+ this.score = score;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChildren = new Drawable[]
+ {
+ new SmoothCircularProgress
+ {
+ Name = "Background circle",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.Gray(47),
+ Alpha = 0.5f,
+ InnerRadius = accuracy_circle_radius + 0.01f, // Extends a little bit into the circle
+ Current = { Value = 1 },
+ },
+ accuracyCircle = new SmoothCircularProgress
+ {
+ Name = "Accuracy circle",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")),
+ InnerRadius = accuracy_circle_radius,
+ },
+ new BufferedContainer
+ {
+ Name = "Graded circles",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.8f),
+ Padding = new MarginPadding(2),
+ Children = new Drawable[]
+ {
+ new SmoothCircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex("#BE0089"),
+ InnerRadius = RANK_CIRCLE_RADIUS,
+ Current = { Value = 1 }
+ },
+ new SmoothCircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex("#0096A2"),
+ InnerRadius = RANK_CIRCLE_RADIUS,
+ Current = { Value = 1 - virtual_ss_percentage }
+ },
+ new SmoothCircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex("#72C904"),
+ InnerRadius = RANK_CIRCLE_RADIUS,
+ Current = { Value = 0.95f }
+ },
+ new SmoothCircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex("#D99D03"),
+ InnerRadius = RANK_CIRCLE_RADIUS,
+ Current = { Value = 0.9f }
+ },
+ new SmoothCircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex("#EA7948"),
+ InnerRadius = RANK_CIRCLE_RADIUS,
+ Current = { Value = 0.8f }
+ },
+ new SmoothCircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex("#FF5858"),
+ InnerRadius = RANK_CIRCLE_RADIUS,
+ Current = { Value = 0.7f }
+ },
+ new RankNotch(0),
+ new RankNotch((float)(1 - virtual_ss_percentage)),
+ new RankNotch(0.95f),
+ new RankNotch(0.9f),
+ new RankNotch(0.8f),
+ new RankNotch(0.7f),
+ new BufferedContainer
+ {
+ Name = "Graded circle mask",
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(1),
+ Blending = new BlendingParameters
+ {
+ Source = BlendingType.DstColor,
+ Destination = BlendingType.OneMinusSrcAlpha,
+ SourceAlpha = BlendingType.One,
+ DestinationAlpha = BlendingType.SrcAlpha
+ },
+ Child = innerMask = new SmoothCircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ InnerRadius = RANK_CIRCLE_RADIUS - 0.01f,
+ }
+ }
+ }
+ },
+ badges = new Container
+ {
+ Name = "Rank badges",
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Vertical = -15, Horizontal = -20 },
+ Children = new[]
+ {
+ new RankBadge(1f, ScoreRank.X),
+ new RankBadge(0.95f, ScoreRank.S),
+ new RankBadge(0.9f, ScoreRank.A),
+ new RankBadge(0.8f, ScoreRank.B),
+ new RankBadge(0.7f, ScoreRank.C),
+ }
+ },
+ rankText = new RankText(score.Rank)
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint);
+
+ using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY, true))
+ innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
+
+ using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY, true))
+ {
+ double targetAccuracy = score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH ? 1 : Math.Min(1 - virtual_ss_percentage, score.Accuracy);
+
+ accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
+
+ foreach (var badge in badges)
+ {
+ if (badge.Accuracy > score.Accuracy)
+ continue;
+
+ using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION, true))
+ badge.Appear();
+ }
+
+ using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true))
+ rankText.Appear();
+ }
+ }
+
+ private double inverseEasing(Easing easing, double targetValue)
+ {
+ double test = 0;
+ double result = 0;
+ int count = 2;
+
+ while (Math.Abs(result - targetValue) > 0.005)
+ {
+ int dir = Math.Sign(targetValue - result);
+
+ test += dir * 1.0 / count;
+ result = Interpolation.ApplyEasing(easing, test);
+
+ count++;
+ }
+
+ return test;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs
new file mode 100644
index 0000000000..76cd408daa
--- /dev/null
+++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankBadge.cs
@@ -0,0 +1,99 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Online.Leaderboards;
+using osu.Game.Scoring;
+using osuTK;
+
+namespace osu.Game.Screens.Ranking.Expanded.Accuracy
+{
+ ///
+ /// Contains a that is positioned around the .
+ ///
+ public class RankBadge : CompositeDrawable
+ {
+ ///
+ /// The accuracy value corresponding to the displayed by this badge.
+ ///
+ public readonly float Accuracy;
+
+ private readonly ScoreRank rank;
+
+ private Drawable rankContainer;
+ private Drawable overlay;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The accuracy value corresponding to .
+ /// The to be displayed in this .
+ public RankBadge(float accuracy, ScoreRank rank)
+ {
+ Accuracy = accuracy;
+ this.rank = rank;
+
+ RelativeSizeAxes = Axes.Both;
+ Alpha = 0;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = rankContainer = new Container
+ {
+ Origin = Anchor.Centre,
+ Size = new Vector2(28, 14),
+ Children = new[]
+ {
+ new DrawableRank(rank),
+ overlay = new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ Masking = true,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = OsuColour.ForRank(rank).Opacity(0.2f),
+ Radius = 10,
+ },
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ }
+ }
+ }
+ };
+ }
+
+ ///
+ /// Shows this .
+ ///
+ public void Appear()
+ {
+ this.FadeIn(50);
+ overlay.FadeIn().FadeOut(500, Easing.In);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // Starts at -90deg (top) and moves counter-clockwise by the accuracy
+ rankContainer.Position = circlePosition(-MathF.PI / 2 - (1 - Accuracy) * MathF.PI * 2);
+ }
+
+ private Vector2 circlePosition(float t)
+ => DrawSize / 2 + new Vector2(MathF.Cos(t), MathF.Sin(t)) * DrawSize / 2;
+ }
+}
diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs
new file mode 100644
index 0000000000..894790b5b6
--- /dev/null
+++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osuTK;
+
+namespace osu.Game.Screens.Ranking.Expanded.Accuracy
+{
+ ///
+ /// A solid "notch" of the that appears at the ends of the rank circles to add separation.
+ ///
+ public class RankNotch : CompositeDrawable
+ {
+ private readonly float position;
+
+ public RankNotch(float position)
+ {
+ this.position = position;
+
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Rotation = position * 360f,
+ Child = new Box
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.Y,
+ Height = AccuracyCircle.RANK_CIRCLE_RADIUS,
+ Width = 1f,
+ Colour = OsuColour.Gray(0.3f),
+ EdgeSmoothness = new Vector2(1f)
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs
new file mode 100644
index 0000000000..8343716e7e
--- /dev/null
+++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs
@@ -0,0 +1,137 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Online.Leaderboards;
+using osu.Game.Scoring;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.Ranking.Expanded.Accuracy
+{
+ ///
+ /// The text that appears in the middle of the displaying the user's rank.
+ ///
+ public class RankText : CompositeDrawable
+ {
+ private readonly ScoreRank rank;
+
+ private BufferedContainer flash;
+ private BufferedContainer superFlash;
+ private GlowingSpriteText rankText;
+
+ public RankText(ScoreRank rank)
+ {
+ this.rank = rank;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Alpha = 0;
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChildren = new Drawable[]
+ {
+ rankText = new GlowingSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ GlowColour = OsuColour.ForRank(rank),
+ Spacing = new Vector2(-15, 0),
+ Text = DrawableRank.GetRankName(rank),
+ Font = OsuFont.Numeric.With(size: 76),
+ UseFullGlyphHeight = false
+ },
+ superFlash = new BufferedContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ BlurSigma = new Vector2(85),
+ Size = new Vector2(600),
+ CacheDrawnFrameBuffer = true,
+ Blending = BlendingParameters.Additive,
+ Alpha = 0,
+ Children = new[]
+ {
+ new Box
+ {
+ Colour = Color4.White,
+ Size = new Vector2(150),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ },
+ },
+ flash = new BufferedContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ BlurSigma = new Vector2(35),
+ BypassAutoSizeAxes = Axes.Both,
+ RelativeSizeAxes = Axes.Both,
+ CacheDrawnFrameBuffer = true,
+ Blending = BlendingParameters.Additive,
+ Alpha = 0,
+ Size = new Vector2(2f), // increase buffer size to allow for scale
+ Scale = new Vector2(1.8f),
+ Children = new[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(-15, 0),
+ Text = DrawableRank.GetRankName(rank),
+ Font = OsuFont.Numeric.With(size: 76),
+ UseFullGlyphHeight = false,
+ Shadow = false
+ },
+ },
+ },
+ };
+ }
+
+ public void Appear()
+ {
+ this.FadeIn();
+
+ if (rank < ScoreRank.A)
+ {
+ this
+ .MoveToOffset(new Vector2(0, -20))
+ .MoveToOffset(new Vector2(0, 20), 200, Easing.OutBounce);
+
+ if (rank <= ScoreRank.D)
+ {
+ this.Delay(700)
+ .RotateTo(5, 150, Easing.In)
+ .MoveToOffset(new Vector2(0, 3), 150, Easing.In);
+ }
+
+ this.FadeInFromZero(200, Easing.OutQuint);
+ return;
+ }
+
+ flash.Colour = OsuColour.ForRank(rank);
+ flash.FadeIn().Then().FadeOut(1200, Easing.OutQuint);
+
+ if (rank >= ScoreRank.S)
+ rankText.ScaleTo(1.05f).ScaleTo(1, 3000, Easing.OutQuint);
+
+ if (rank >= ScoreRank.X)
+ {
+ flash.FadeIn().Then().FadeOut(3000);
+ superFlash.FadeIn().Then().FadeOut(800, Easing.OutQuint);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/SmoothCircularProgress.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/SmoothCircularProgress.cs
new file mode 100644
index 0000000000..106af31cae
--- /dev/null
+++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/SmoothCircularProgress.cs
@@ -0,0 +1,126 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Transforms;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics;
+using osuTK;
+
+namespace osu.Game.Screens.Ranking.Expanded.Accuracy
+{
+ ///
+ /// Contains a with smoothened edges.
+ ///
+ public class SmoothCircularProgress : CompositeDrawable
+ {
+ public Bindable Current
+ {
+ get => progress.Current;
+ set => progress.Current = value;
+ }
+
+ public float InnerRadius
+ {
+ get => progress.InnerRadius;
+ set
+ {
+ progress.InnerRadius = value;
+ innerSmoothingContainer.Size = new Vector2(1 - value);
+ smoothingWedge.Height = value / 2;
+ }
+ }
+
+ private readonly CircularProgress progress;
+ private readonly Container innerSmoothingContainer;
+ private readonly Drawable smoothingWedge;
+
+ public SmoothCircularProgress()
+ {
+ Container smoothingWedgeContainer;
+
+ InternalChild = new BufferedContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ progress = new CircularProgress { RelativeSizeAxes = Axes.Both },
+ smoothingWedgeContainer = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Child = smoothingWedge = new Box
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.Y,
+ Width = 1f,
+ EdgeSmoothness = new Vector2(2, 0),
+ }
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(-1),
+ Child = new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ BorderThickness = 2,
+ Masking = true,
+ BorderColour = OsuColour.Gray(0.5f).Opacity(0.75f),
+ Blending = new BlendingParameters
+ {
+ AlphaEquation = BlendingEquation.ReverseSubtract,
+ },
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true
+ }
+ }
+ },
+ innerSmoothingContainer = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Size = Vector2.Zero,
+ Padding = new MarginPadding(-1),
+ Child = new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ BorderThickness = 2,
+ BorderColour = OsuColour.Gray(0.5f).Opacity(0.75f),
+ Masking = true,
+ Blending = new BlendingParameters
+ {
+ AlphaEquation = BlendingEquation.ReverseSubtract,
+ },
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true
+ }
+ }
+ },
+ }
+ };
+
+ Current.BindValueChanged(c =>
+ {
+ smoothingWedgeContainer.Alpha = c.NewValue > 0 ? 1 : 0;
+ smoothingWedgeContainer.Rotation = (float)(360 * c.NewValue);
+ }, true);
+ }
+
+ public TransformSequence FillTo(double newValue, double duration = 0, Easing easing = Easing.None)
+ => progress.FillTo(newValue, duration, easing);
+ }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 647f05b428..54f1ad2845 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 0e5c64cf0f..816a430b52 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -71,7 +71,7 @@
-
+
@@ -79,7 +79,7 @@
-
+