diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index 8076799064..be1cd8a35c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -7,27 +7,16 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Framework.Threading; using osu.Framework.Utils; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Scoring; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Scoring; -using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; -using osu.Game.Screens.Play.HUD.HitErrorMeters; using osuTK; namespace osu.Game.Tests.Visual.Gameplay @@ -40,8 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(GameplayState))] private GameplayState gameplayState; - [Cached(typeof(DrawableRuleset))] - private TestDrawableRuleset drawableRuleset = new TestDrawableRuleset(); + private OsuHitWindows hitWindows = new OsuHitWindows(); private double prev; @@ -64,42 +52,38 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestBasic() { - AddStep("Create Display", () => recreateDisplay(new OsuHitWindows(), 5)); + AddStep("Create Display", () => recreateDisplay()); - AddRepeatStep("Set UR to 250ms", () => setUR(25), 20); + AddRepeatStep("Set UR to 250", () => setUR(25), 20); AddStep("Reset UR", () => { scoreProcessor.Reset(); - recreateDisplay(new OsuHitWindows(), 5); + recreateDisplay(); }); - AddRepeatStep("Set UR to 100ms", () => setUR(10), 20); + AddRepeatStep("Set UR to 100", () => setUR(10), 20); AddStep("Reset UR", () => { scoreProcessor.Reset(); - recreateDisplay(new OsuHitWindows(), 5); + recreateDisplay(); }); - AddRepeatStep("Set UR to 0 (+50ms)", () => newJudgement(50), 20); + AddRepeatStep("Set UR to 0 (+50ms offset)", () => newJudgement(50), 10); AddStep("Reset UR", () => { scoreProcessor.Reset(); - recreateDisplay(new OsuHitWindows(), 5); + recreateDisplay(); }); - AddRepeatStep("Set UR to 0 (-50ms)", () => newJudgement(-50), 20); + AddRepeatStep("Set UR to 0 (-50 offset)", () => newJudgement(-50), 10); - AddRepeatStep("New random judgement", () => newJudgement(), 40); + AddRepeatStep("Random Judgements", () => newJudgement(), 20); } - private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) + private void recreateDisplay() { - hitWindows?.SetDifficulty(overallDifficulty); - - drawableRuleset.HitWindows = hitWindows; - Clear(); Add(new UnstableRateCounter @@ -108,26 +92,11 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.Centre, Scale = new Vector2(5), }); - - Add(new BarHitErrorMeter - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.CentreLeft, - Rotation = 270, - }); - - Add(new ColourHitErrorMeter - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.CentreLeft, - Rotation = 270, - Margin = new MarginPadding { Left = 50 } - }); } private void newJudgement(double offset = 0, HitResult result = HitResult.Perfect) { - scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = drawableRuleset.HitWindows }, new Judgement()) + scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = result, @@ -137,7 +106,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void setUR(double UR = 0, HitResult result = HitResult.Perfect) { double placement = prev > 0 ? -UR : UR; - scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = drawableRuleset.HitWindows }, new Judgement()) + scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) { TimeOffset = placement, Type = result, @@ -145,43 +114,6 @@ namespace osu.Game.Tests.Visual.Gameplay prev = placement; } - [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] - private class TestDrawableRuleset : DrawableRuleset - { - public HitWindows HitWindows; - - public override IEnumerable Objects => new[] { new HitCircle { HitWindows = HitWindows } }; - - public override event Action NewResult; - public override event Action RevertResult; - - public override Playfield Playfield { get; } - public override Container Overlays { get; } - public override Container FrameStableComponents { get; } - public override IFrameStableClock FrameStableClock { get; } - internal override bool FrameStablePlayback { get; set; } - public override IReadOnlyList Mods { get; } - - public override double GameplayStartTime { get; } - public override GameplayCursorContainer Cursor { get; } - - public TestDrawableRuleset() - : base(new OsuRuleset()) - { - // won't compile without this. - NewResult?.Invoke(null); - RevertResult?.Invoke(null); - } - - public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); - - public override void SetRecordTarget(Score score) => throw new NotImplementedException(); - - public override void RequestResume(Action continueResume) => throw new NotImplementedException(); - - public override void CancelResume() => throw new NotImplementedException(); - } - private class TestScoreProcessor : ScoreProcessor { public void Reset() => base.Reset(false); diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 3e3240c683..401924a898 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -1,11 +1,16 @@ // 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 System.Linq; using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -13,8 +18,10 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Screens.Play.HUD { @@ -28,19 +35,16 @@ namespace osu.Game.Screens.Play.HUD private const float alpha_when_invalid = 0.3f; + private List hitList = new List(); + [CanBeNull] [Resolved(CanBeNull = true)] private ScoreProcessor scoreProcessor { get; set; } - [Resolved(CanBeNull = true)] - [CanBeNull] - private GameplayState gameplayState { get; set; } - private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); - public UnstableRateCounter() { - Current.Value = DisplayedCount = 0.0; + Current.Value = 0.0; } [BackgroundDependencyLoader] @@ -55,63 +59,118 @@ namespace osu.Game.Screens.Play.HUD if (scoreProcessor != null) { - scoreProcessor.NewJudgement += onJudgementChanged; + scoreProcessor.NewJudgement += onJudgementAdded; scoreProcessor.JudgementReverted += onJudgementChanged; } } private bool isValid; - - protected bool IsValid + private void setValid(bool valid) { - set - { - if (value == isValid) - return; - - isValid = value; - DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); - } + isValid = valid; + DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); } + private void onJudgementAdded(JudgementResult judgement) + { + if (!(judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows) && judgement.IsHit) + { + hitList.Add(judgement.TimeOffset); + } + updateUR(); + } + + // Only populate via the score if the user has moved the current location. private void onJudgementChanged(JudgementResult judgement) { - if (gameplayState == null) - { - isValid = false; - return; - } + ScoreInfo currentScore = new ScoreInfo(); + scoreProcessor.PopulateScore(currentScore); + hitList = currentScore.HitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()) + .Select(ev => ev.TimeOffset).ToList(); + updateUR(); + } - double ur = new UnstableRate(gameplayState.Score.ScoreInfo.HitEvents).Value; - if (double.IsNaN(ur)) // Error handling: If the user misses the first few notes, the UR is NaN. + private void updateUR() + { + if (hitList.Count > 0) { - isValid = false; - return; + double mean = hitList.Average(); + double squares = hitList.Select(offset => Math.Pow(offset - mean, 2)).Sum(); + Current.Value = Math.Sqrt(squares / hitList.Count) * 10; + setValid(true); + } + else + { + setValid(false); } - Current.Value = ur; - IsValid = true; } protected override LocalisableString FormatCount(double count) { - return count.ToLocalisableString("0.00 UR"); + return count.ToString("0.00"); } - protected override OsuSpriteText CreateSpriteText() - => base.CreateSpriteText().With(s => - { - s.Font = s.Font.With(size: 12f, fixedWidth: true); - s.Alpha = alpha_when_invalid; - }); + protected override IHasText CreateText() => new TextComponent + { + Alpha = alpha_when_invalid, + }; protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (scoreProcessor != null) - scoreProcessor.NewJudgement -= onJudgementChanged; - + { + scoreProcessor.NewJudgement -= onJudgementAdded; + scoreProcessor.JudgementReverted -= onJudgementChanged; + } loadCancellationSource?.Cancel(); } + + private class TextComponent : CompositeDrawable, IHasText + { + public LocalisableString Text + { + get => intPart.Text; + set { + //Not too sure about this, is there a better way to go about doing this? + splitValue = value.ToString().Split('.'); + intPart.Text = splitValue[0]; + decimalPart.Text = $".{splitValue[1]} UR"; + } + } + + private string[] splitValue; + private readonly OsuSpriteText intPart; + private readonly OsuSpriteText decimalPart; + + public TextComponent() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + //Spacing = new Vector2(2), + Children = new Drawable[] + { + intPart = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) + }, + decimalPart = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = @" UR", + Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), + Padding = new MarginPadding { Bottom = 1.5f }, + } + } + }; + } + } } }