1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 17:27:24 +08:00

Adjust catch score grade cutoffs

This commit is contained in:
Dan Balasescu 2023-11-29 19:05:24 +09:00
parent 448ca6fa27
commit 295a1b01d6
No known key found for this signature in database
4 changed files with 160 additions and 76 deletions

View File

@ -4,11 +4,19 @@
using System; using System;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Catch.Scoring namespace osu.Game.Rulesets.Catch.Scoring
{ {
public partial class CatchScoreProcessor : ScoreProcessor 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 int combo_cap = 200;
private const double combo_base = 4; private const double combo_base = 4;
@ -26,5 +34,50 @@ namespace osu.Game.Rulesets.Catch.Scoring
protected override double GetComboScoreChange(JudgementResult result) 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)); => 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);
}
}
} }
} }

View File

@ -9,6 +9,8 @@ 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.Game.Online.API.Requests.Responses; 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.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
@ -22,31 +24,48 @@ namespace osu.Game.Tests.Visual.Ranking
{ {
public partial class TestSceneAccuracyCircle : OsuTestScene public partial class TestSceneAccuracyCircle : OsuTestScene
{ {
[TestCase(0)] [Test]
[TestCase(0.2)] public void TestOsuRank()
[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)
{ {
var score = createScore(accuracy, ScoreProcessor.RankFromAccuracy(accuracy)); addCircleStep(createScore(0, new OsuRuleset()));
addCircleStep(createScore(0.5, new OsuRuleset()));
addCircleStep(score); 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[] 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, User = new APIUser
Username = "peppy", {
}, Id = 2,
BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, Username = "peppy",
Ruleset = new OsuRuleset().RulesetInfo, },
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
TotalScore = 2845370, Ruleset = ruleset.RulesetInfo,
Accuracy = accuracy, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
MaxCombo = 999, TotalScore = 2845370,
Rank = rank, Accuracy = accuracy,
Date = DateTimeOffset.Now, MaxCombo = 999,
Statistics = Rank = scoreProcessor.RankFromAccuracy(accuracy),
{ Date = DateTimeOffset.Now,
{ HitResult.Miss, 1 }, Statistics =
{ HitResult.Meh, 50 }, {
{ HitResult.Good, 100 }, { HitResult.Miss, 1 },
{ HitResult.Great, 300 }, { HitResult.Meh, 50 },
} { HitResult.Good, 100 },
}; { HitResult.Great, 300 },
}
};
}
} }
} }

View File

@ -446,7 +446,7 @@ namespace osu.Game.Rulesets.Scoring
/// <summary> /// <summary>
/// Given an accuracy (0..1), return the correct <see cref="ScoreRank"/>. /// Given an accuracy (0..1), return the correct <see cref="ScoreRank"/>.
/// </summary> /// </summary>
public static ScoreRank RankFromAccuracy(double accuracy) public virtual ScoreRank RankFromAccuracy(double accuracy)
{ {
if (accuracy == accuracy_cutoff_x) if (accuracy == accuracy_cutoff_x)
return ScoreRank.X; return ScoreRank.X;
@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Scoring
/// Given a <see cref="ScoreRank"/>, return the cutoff accuracy (0..1). /// Given a <see cref="ScoreRank"/>, return the cutoff accuracy (0..1).
/// Accuracy must be greater than or equal to the cutoff to qualify for the provided rank. /// Accuracy must be greater than or equal to the cutoff to qualify for the provided rank.
/// </summary> /// </summary>
public static double AccuracyCutoffFromRank(ScoreRank rank) public virtual double AccuracyCutoffFromRank(ScoreRank rank)
{ {
switch (rank) switch (rank)
{ {

View File

@ -29,13 +29,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
/// </summary> /// </summary>
public partial class AccuracyCircle : CompositeDrawable 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);
/// <summary> /// <summary>
/// Duration for the transforms causing this component to appear. /// Duration for the transforms causing this component to appear.
/// </summary> /// </summary>
@ -110,12 +103,26 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
private double lastTickPlaybackTime; private double lastTickPlaybackTime;
private bool isTicking; 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; private readonly bool withFlair;
public AccuracyCircle(ScoreInfo score, bool withFlair = false) public AccuracyCircle(ScoreInfo score, bool withFlair = false)
{ {
this.score = score; this.score = score;
this.withFlair = withFlair; 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] [BackgroundDependencyLoader]
@ -158,49 +165,49 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.X), Colour = OsuColour.ForRank(ScoreRank.X),
InnerRadius = RANK_CIRCLE_RADIUS, InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = accuracy_x } Current = { Value = accuracyX }
}, },
new CircularProgress new CircularProgress
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.S), Colour = OsuColour.ForRank(ScoreRank.S),
InnerRadius = RANK_CIRCLE_RADIUS, InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = accuracy_x - virtual_ss_percentage } Current = { Value = accuracyX - virtual_ss_percentage }
}, },
new CircularProgress new CircularProgress
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.A), Colour = OsuColour.ForRank(ScoreRank.A),
InnerRadius = RANK_CIRCLE_RADIUS, InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = accuracy_s } Current = { Value = accuracyS }
}, },
new CircularProgress new CircularProgress
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.B), Colour = OsuColour.ForRank(ScoreRank.B),
InnerRadius = RANK_CIRCLE_RADIUS, InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = accuracy_a } Current = { Value = accuracyA }
}, },
new CircularProgress new CircularProgress
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.C), Colour = OsuColour.ForRank(ScoreRank.C),
InnerRadius = RANK_CIRCLE_RADIUS, InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = accuracy_b } Current = { Value = accuracyB }
}, },
new CircularProgress new CircularProgress
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = OsuColour.ForRank(ScoreRank.D), Colour = OsuColour.ForRank(ScoreRank.D),
InnerRadius = RANK_CIRCLE_RADIUS, InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = accuracy_c } Current = { Value = accuracyC }
}, },
new RankNotch((float)accuracy_x), new RankNotch((float)accuracyX),
new RankNotch((float)(accuracy_x - virtual_ss_percentage)), new RankNotch((float)(accuracyX - virtual_ss_percentage)),
new RankNotch((float)accuracy_s), new RankNotch((float)accuracyS),
new RankNotch((float)accuracy_a), new RankNotch((float)accuracyA),
new RankNotch((float)accuracy_b), new RankNotch((float)accuracyB),
new RankNotch((float)accuracy_c), new RankNotch((float)accuracyC),
new BufferedContainer new BufferedContainer
{ {
Name = "Graded circle mask", Name = "Graded circle mask",
@ -229,12 +236,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
Children = new[] Children = new[]
{ {
// The S and A badges are moved down slightly to prevent collision with the SS badge. // 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(accuracyX, accuracyX, getRank(ScoreRank.X)),
new RankBadge(accuracy_s, Interpolation.Lerp(accuracy_s, (accuracy_x - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)), new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - 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(accuracyA, Interpolation.Lerp(accuracyA, accuracyS, 0.25), getRank(ScoreRank.A)),
new RankBadge(accuracy_b, Interpolation.Lerp(accuracy_b, accuracy_a, 0.5), getRank(ScoreRank.B)), new RankBadge(accuracyB, Interpolation.Lerp(accuracyB, accuracyA, 0.5), getRank(ScoreRank.B)),
new RankBadge(accuracy_c, Interpolation.Lerp(accuracy_c, accuracy_b, 0.5), getRank(ScoreRank.C)), new RankBadge(accuracyC, Interpolation.Lerp(accuracyC, accuracyB, 0.5), getRank(ScoreRank.C)),
new RankBadge(accuracy_d, Interpolation.Lerp(accuracy_d, accuracy_c, 0.5), getRank(ScoreRank.D)), new RankBadge(accuracyD, Interpolation.Lerp(accuracyD, accuracyC, 0.5), getRank(ScoreRank.D)),
} }
}, },
rankText = new RankText(score.Rank) rankText = new RankText(score.Rank)
@ -280,10 +287,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
double targetAccuracy = score.Accuracy; double targetAccuracy = score.Accuracy;
double[] notchPercentages = double[] notchPercentages =
{ {
accuracy_s, accuracyS,
accuracy_a, accuracyA,
accuracy_b, accuracyB,
accuracy_c, 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), // 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) if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH)
targetAccuracy = 1; targetAccuracy = 1;
else 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. // 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. // 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) if (badge.Accuracy > score.Accuracy)
continue; 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(); badge.Appear();