diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 9323296b7f..66c76f9b17 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -4,11 +4,19 @@ using System; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Catch.Scoring { public partial class CatchScoreProcessor : ScoreProcessor { + private const double accuracy_cutoff_x = 1; + private const double accuracy_cutoff_s = 0.98; + private const double accuracy_cutoff_a = 0.94; + private const double accuracy_cutoff_b = 0.9; + private const double accuracy_cutoff_c = 0.85; + private const double accuracy_cutoff_d = 0; + private const int combo_cap = 200; private const double combo_base = 4; @@ -26,5 +34,50 @@ namespace osu.Game.Rulesets.Catch.Scoring protected override double GetComboScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base)); + + public override ScoreRank RankFromAccuracy(double accuracy) + { + if (accuracy == accuracy_cutoff_x) + return ScoreRank.X; + if (accuracy >= accuracy_cutoff_s) + return ScoreRank.S; + if (accuracy >= accuracy_cutoff_a) + return ScoreRank.A; + if (accuracy >= accuracy_cutoff_b) + return ScoreRank.B; + if (accuracy >= accuracy_cutoff_c) + return ScoreRank.C; + + return ScoreRank.D; + } + + public override double AccuracyCutoffFromRank(ScoreRank rank) + { + switch (rank) + { + case ScoreRank.X: + case ScoreRank.XH: + return accuracy_cutoff_x; + + case ScoreRank.S: + case ScoreRank.SH: + return accuracy_cutoff_s; + + case ScoreRank.A: + return accuracy_cutoff_a; + + case ScoreRank.B: + return accuracy_cutoff_b; + + case ScoreRank.C: + return accuracy_cutoff_c; + + case ScoreRank.D: + return accuracy_cutoff_d; + + default: + throw new ArgumentOutOfRangeException(nameof(rank), rank, null); + } + } } } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 03b168c72c..435dd77120 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -9,6 +9,8 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -22,31 +24,48 @@ namespace osu.Game.Tests.Visual.Ranking { public partial class TestSceneAccuracyCircle : OsuTestScene { - [TestCase(0)] - [TestCase(0.2)] - [TestCase(0.5)] - [TestCase(0.6999)] - [TestCase(0.7)] - [TestCase(0.75)] - [TestCase(0.7999)] - [TestCase(0.8)] - [TestCase(0.85)] - [TestCase(0.8999)] - [TestCase(0.9)] - [TestCase(0.925)] - [TestCase(0.9499)] - [TestCase(0.95)] - [TestCase(0.975)] - [TestCase(0.9999)] - [TestCase(1)] - public void TestRank(double accuracy) + [Test] + public void TestOsuRank() { - var score = createScore(accuracy, ScoreProcessor.RankFromAccuracy(accuracy)); - - addCircleStep(score); + addCircleStep(createScore(0, new OsuRuleset())); + addCircleStep(createScore(0.5, new OsuRuleset())); + addCircleStep(createScore(0.699, new OsuRuleset())); + addCircleStep(createScore(0.7, new OsuRuleset())); + addCircleStep(createScore(0.75, new OsuRuleset())); + addCircleStep(createScore(0.799, new OsuRuleset())); + addCircleStep(createScore(0.8, new OsuRuleset())); + addCircleStep(createScore(0.85, new OsuRuleset())); + addCircleStep(createScore(0.899, new OsuRuleset())); + addCircleStep(createScore(0.9, new OsuRuleset())); + addCircleStep(createScore(0.925, new OsuRuleset())); + addCircleStep(createScore(0.9499, new OsuRuleset())); + addCircleStep(createScore(0.95, new OsuRuleset())); + addCircleStep(createScore(0.975, new OsuRuleset())); + addCircleStep(createScore(0.99, new OsuRuleset())); + addCircleStep(createScore(1, new OsuRuleset())); } - private void addCircleStep(ScoreInfo score) => AddStep("add panel", () => + [Test] + public void TestCatchRank() + { + addCircleStep(createScore(0, new CatchRuleset())); + addCircleStep(createScore(0.5, new CatchRuleset())); + addCircleStep(createScore(0.8499, new CatchRuleset())); + addCircleStep(createScore(0.85, new CatchRuleset())); + addCircleStep(createScore(0.875, new CatchRuleset())); + addCircleStep(createScore(0.899, new CatchRuleset())); + addCircleStep(createScore(0.9, new CatchRuleset())); + addCircleStep(createScore(0.925, new CatchRuleset())); + addCircleStep(createScore(0.9399, new CatchRuleset())); + addCircleStep(createScore(0.94, new CatchRuleset())); + addCircleStep(createScore(0.9675, new CatchRuleset())); + addCircleStep(createScore(0.9799, new CatchRuleset())); + addCircleStep(createScore(0.98, new CatchRuleset())); + addCircleStep(createScore(0.99, new CatchRuleset())); + addCircleStep(createScore(1, new CatchRuleset())); + } + + private void addCircleStep(ScoreInfo score) => AddStep($"add panel ({score.DisplayAccuracy})", () => { Children = new Drawable[] { @@ -73,28 +92,33 @@ namespace osu.Game.Tests.Visual.Ranking }; }); - private ScoreInfo createScore(double accuracy, ScoreRank rank) => new ScoreInfo + private ScoreInfo createScore(double accuracy, Ruleset ruleset) { - User = new APIUser + var scoreProcessor = ruleset.CreateScoreProcessor(); + + return new ScoreInfo { - Id = 2, - Username = "peppy", - }, - BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Ruleset = new OsuRuleset().RulesetInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 2845370, - Accuracy = accuracy, - MaxCombo = 999, - Rank = rank, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; + User = new APIUser + { + Id = 2, + Username = "peppy", + }, + BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, + Ruleset = ruleset.RulesetInfo, + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + TotalScore = 2845370, + Accuracy = accuracy, + MaxCombo = 999, + Rank = scoreProcessor.RankFromAccuracy(accuracy), + Date = DateTimeOffset.Now, + Statistics = + { + { HitResult.Miss, 1 }, + { HitResult.Meh, 50 }, + { HitResult.Good, 100 }, + { HitResult.Great, 300 }, + } + }; + } } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4e899479bd..92336b2c21 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -446,7 +446,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Given an accuracy (0..1), return the correct . /// - public static ScoreRank RankFromAccuracy(double accuracy) + public virtual ScoreRank RankFromAccuracy(double accuracy) { if (accuracy == accuracy_cutoff_x) return ScoreRank.X; @@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Scoring /// Given a , return the cutoff accuracy (0..1). /// Accuracy must be greater than or equal to the cutoff to qualify for the provided rank. /// - public static double AccuracyCutoffFromRank(ScoreRank rank) + public virtual double AccuracyCutoffFromRank(ScoreRank rank) { switch (rank) { diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 2ec4270c3c..80ff872312 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -29,13 +29,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy /// public partial class AccuracyCircle : CompositeDrawable { - private static readonly double accuracy_x = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.X); - private static readonly double accuracy_s = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.S); - private static readonly double accuracy_a = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.A); - private static readonly double accuracy_b = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.B); - private static readonly double accuracy_c = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.C); - private static readonly double accuracy_d = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.D); - /// /// Duration for the transforms causing this component to appear. /// @@ -110,12 +103,26 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private double lastTickPlaybackTime; private bool isTicking; + private readonly double accuracyX; + private readonly double accuracyS; + private readonly double accuracyA; + private readonly double accuracyB; + private readonly double accuracyC; + private readonly double accuracyD; private readonly bool withFlair; public AccuracyCircle(ScoreInfo score, bool withFlair = false) { this.score = score; this.withFlair = withFlair; + + ScoreProcessor scoreProcessor = score.Ruleset.CreateInstance().CreateScoreProcessor(); + accuracyX = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.X); + accuracyS = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.S); + accuracyA = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.A); + accuracyB = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.B); + accuracyC = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.C); + accuracyD = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.D); } [BackgroundDependencyLoader] @@ -158,49 +165,49 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.X), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_x } + Current = { Value = accuracyX } }, new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.S), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_x - virtual_ss_percentage } + Current = { Value = accuracyX - virtual_ss_percentage } }, new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.A), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_s } + Current = { Value = accuracyS } }, new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.B), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_a } + Current = { Value = accuracyA } }, new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.C), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_b } + Current = { Value = accuracyB } }, new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.D), InnerRadius = RANK_CIRCLE_RADIUS, - Current = { Value = accuracy_c } + Current = { Value = accuracyC } }, - new RankNotch((float)accuracy_x), - new RankNotch((float)(accuracy_x - virtual_ss_percentage)), - new RankNotch((float)accuracy_s), - new RankNotch((float)accuracy_a), - new RankNotch((float)accuracy_b), - new RankNotch((float)accuracy_c), + new RankNotch((float)accuracyX), + new RankNotch((float)(accuracyX - virtual_ss_percentage)), + new RankNotch((float)accuracyS), + new RankNotch((float)accuracyA), + new RankNotch((float)accuracyB), + new RankNotch((float)accuracyC), new BufferedContainer { Name = "Graded circle mask", @@ -229,12 +236,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Children = new[] { // The S and A badges are moved down slightly to prevent collision with the SS badge. - new RankBadge(accuracy_x, accuracy_x, getRank(ScoreRank.X)), - new RankBadge(accuracy_s, Interpolation.Lerp(accuracy_s, (accuracy_x - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)), - new RankBadge(accuracy_a, Interpolation.Lerp(accuracy_a, accuracy_s, 0.25), getRank(ScoreRank.A)), - new RankBadge(accuracy_b, Interpolation.Lerp(accuracy_b, accuracy_a, 0.5), getRank(ScoreRank.B)), - new RankBadge(accuracy_c, Interpolation.Lerp(accuracy_c, accuracy_b, 0.5), getRank(ScoreRank.C)), - new RankBadge(accuracy_d, Interpolation.Lerp(accuracy_d, accuracy_c, 0.5), getRank(ScoreRank.D)), + new RankBadge(accuracyX, accuracyX, getRank(ScoreRank.X)), + new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)), + new RankBadge(accuracyA, Interpolation.Lerp(accuracyA, accuracyS, 0.25), getRank(ScoreRank.A)), + new RankBadge(accuracyB, Interpolation.Lerp(accuracyB, accuracyA, 0.5), getRank(ScoreRank.B)), + new RankBadge(accuracyC, Interpolation.Lerp(accuracyC, accuracyB, 0.5), getRank(ScoreRank.C)), + new RankBadge(accuracyD, Interpolation.Lerp(accuracyD, accuracyC, 0.5), getRank(ScoreRank.D)), } }, rankText = new RankText(score.Rank) @@ -280,10 +287,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy double targetAccuracy = score.Accuracy; double[] notchPercentages = { - accuracy_s, - accuracy_a, - accuracy_b, - accuracy_c, + accuracyS, + accuracyA, + accuracyB, + accuracyC, }; // Ensure the gauge overshoots or undershoots a bit so it doesn't land in the gaps of the inner graded circle (caused by `RankNotch`es), @@ -302,7 +309,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH) targetAccuracy = 1; else - targetAccuracy = Math.Min(accuracy_x - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy); + targetAccuracy = Math.Min(accuracyX - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy); // The accuracy circle gauge visually fills up a bit too much. // This wouldn't normally matter but we want it to align properly with the inner graded circle in the above cases. @@ -339,7 +346,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (badge.Accuracy > score.Accuracy) continue; - using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracy_x - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) + using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) { badge.Appear();