From 9aa3021bbc5f1f2ba2383cb96aef012d19ba5713 Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Wed, 3 Nov 2021 22:22:44 -0600 Subject: [PATCH 01/26] Added Unstable Rate Counter as a skinable element --- .../Screens/Play/HUD/UnstableRateCounter.cs | 124 ++++++++++++++++++ osu.Game/Skinning/DefaultSkin.cs | 14 +- 2 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/UnstableRateCounter.cs diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs new file mode 100644 index 0000000000..7109ea1995 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -0,0 +1,124 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + public class UnstableRateCounter : RollingCounter, ISkinnableDrawable + { + public bool UsesFixedAnchor { get; set; } + + protected override bool IsRollingProportional => true; + + protected override double RollingDuration => 750; + + private const float alpha_when_invalid = 0.3f; + + [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; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) + { + Colour = colours.BlueLighter; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (scoreProcessor != null) + { + scoreProcessor.NewJudgement += onJudgementChanged; + scoreProcessor.JudgementReverted += onJudgementChanged; + } + } + + private bool isValid; + + protected bool IsValid + { + set + { + if (value == isValid) + return; + + isValid = value; + DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); + } + } + + private void onJudgementChanged(JudgementResult judgement) + { + + if (gameplayState == null) + { + isValid = false; + return; + } + + 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. + { + isValid = false; + return; + } + Current.Value = ur; + IsValid = true; + } + + protected override LocalisableString FormatCount(double count) + { + return count.ToLocalisableString("0.00 UR"); + } + + + protected override OsuSpriteText CreateSpriteText() + => base.CreateSpriteText().With(s => { + s.Font = s.Font.With(size: 12f, fixedWidth: true); + s.Alpha = alpha_when_invalid; + }); + + + + + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (scoreProcessor != null) + scoreProcessor.NewJudgement -= onJudgementChanged; + + loadCancellationSource?.Cancel(); + } + + + } +} diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index c377f16f8b..e887ccb2e3 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -68,6 +68,7 @@ namespace osu.Game.Skinning var accuracy = container.OfType().FirstOrDefault(); var combo = container.OfType().FirstOrDefault(); var ppCounter = container.OfType().FirstOrDefault(); + var unstableRate = container.OfType().FirstOrDefault(); if (score != null) { @@ -84,9 +85,17 @@ namespace osu.Game.Skinning if (ppCounter != null) { ppCounter.Y = score.Position.Y + ppCounter.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).Y - 4; - ppCounter.Origin = Anchor.TopCentre; + ppCounter.X = ppCounter.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding; + ppCounter.Origin = Anchor.TopRight; ppCounter.Anchor = Anchor.TopCentre; } + if (unstableRate != null) + { + unstableRate.Y = ppCounter.Position.Y; + unstableRate.X = -unstableRate.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding; + unstableRate.Origin = Anchor.TopLeft; + unstableRate.Anchor = Anchor.TopCentre; + } if (accuracy != null) { @@ -130,7 +139,8 @@ namespace osu.Game.Skinning new SongProgress(), new BarHitErrorMeter(), new BarHitErrorMeter(), - new PerformancePointsCounter() + new PerformancePointsCounter(), + new UnstableRateCounter() } }; From 223efe55d5f8c0485f374c70a75ff32ee34cd7f9 Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Thu, 4 Nov 2021 09:23:29 -0600 Subject: [PATCH 02/26] Added Tests for UR counter --- .../Gameplay/TestSceneUnstableRateCounter.cs | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs new file mode 100644 index 0000000000..12bca1122a --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -0,0 +1,210 @@ +// 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.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 +{ + public class TestSceneUnstableRateCounter : OsuTestScene + { + [Cached(typeof(ScoreProcessor))] + private TestScoreProcessor scoreProcessor = new TestScoreProcessor(); + + [Cached(typeof(GameplayState))] + private GameplayState gameplayState; + + + [Cached(typeof(DrawableRuleset))] + private TestDrawableRuleset drawableRuleset = new TestDrawableRuleset(); + + private double prev; + + + + public TestSceneUnstableRateCounter() + { + Score score = new Score + { + ScoreInfo = new ScoreInfo(), + }; + gameplayState = new GameplayState(null, null, null, score); + scoreProcessor.NewJudgement += result => scoreProcessor.PopulateScore(score.ScoreInfo); + } + + + + + + [SetUpSteps] + public void SetUp() + { + AddStep("Reset Score Processor", () => scoreProcessor.Reset()); + } + + [Test] + public void TestBasic() + { + AddStep("Create Display", () => recreateDisplay(new OsuHitWindows(), 5)); + + + AddRepeatStep("Set UR to 250ms", () => setUR(25), 20); + + AddStep("Reset UR", () => + { + scoreProcessor.Reset(); + recreateDisplay(new OsuHitWindows(), 5); + }); + + AddRepeatStep("Set UR to 100ms", () => setUR(10), 20); + + AddStep("Reset UR", () => + { + scoreProcessor.Reset(); + recreateDisplay(new OsuHitWindows(), 5); + }); + + AddRepeatStep("Set UR to 0 (+50ms)", () => newJudgement(50), 20); + + AddStep("Reset UR", () => + { + scoreProcessor.Reset(); + recreateDisplay(new OsuHitWindows(), 5); + }); + + AddRepeatStep("Set UR to 0 (-50ms)", () => newJudgement(-50), 20); + + AddRepeatStep("New random judgement", () => newJudgement(), 40); + } + private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) + { + hitWindows?.SetDifficulty(overallDifficulty); + + drawableRuleset.HitWindows = hitWindows; + + Clear(); + + Add(new UnstableRateCounter + { + Anchor = Anchor.Centre, + 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()) + { + TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, + Type = result, + }); + } + + 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()) + { + TimeOffset = placement, + Type = result, + }); + 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); + } + } +} From c5688885009ac1ec47b61b601a838354b9c4112e Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Thu, 4 Nov 2021 09:47:52 -0600 Subject: [PATCH 03/26] Edited to remove unessicary blank lines --- .../Gameplay/TestSceneUnstableRateCounter.cs | 18 ------------------ .../Screens/Play/HUD/UnstableRateCounter.cs | 7 ++----- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index 12bca1122a..7f0228c4b0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -46,8 +46,6 @@ namespace osu.Game.Tests.Visual.Gameplay private double prev; - - public TestSceneUnstableRateCounter() { Score score = new Score @@ -58,10 +56,6 @@ namespace osu.Game.Tests.Visual.Gameplay scoreProcessor.NewJudgement += result => scoreProcessor.PopulateScore(score.ScoreInfo); } - - - - [SetUpSteps] public void SetUp() { @@ -73,7 +67,6 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("Create Display", () => recreateDisplay(new OsuHitWindows(), 5)); - AddRepeatStep("Set UR to 250ms", () => setUR(25), 20); AddStep("Reset UR", () => @@ -133,11 +126,6 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - - - - - private void newJudgement(double offset = 0, HitResult result = HitResult.Perfect) { scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = drawableRuleset.HitWindows }, new Judgement()) @@ -159,12 +147,6 @@ namespace osu.Game.Tests.Visual.Gameplay prev = placement; } - - - - - - [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] private class TestDrawableRuleset : DrawableRuleset { diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 7109ea1995..489a7ff721 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -100,15 +100,12 @@ namespace osu.Game.Screens.Play.HUD protected override OsuSpriteText CreateSpriteText() - => base.CreateSpriteText().With(s => { + => base.CreateSpriteText().With(s => + { s.Font = s.Font.With(size: 12f, fixedWidth: true); s.Alpha = alpha_when_invalid; }); - - - - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From a76878e05350723e4d598be1c0832ac51525c782 Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Thu, 4 Nov 2021 09:52:16 -0600 Subject: [PATCH 04/26] A bit more formatting --- .../Visual/Gameplay/TestSceneUnstableRateCounter.cs | 2 -- osu.Game/Screens/Play/HUD/UnstableRateCounter.cs | 4 ---- 2 files changed, 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index 7f0228c4b0..8076799064 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(GameplayState))] private GameplayState gameplayState; - [Cached(typeof(DrawableRuleset))] private TestDrawableRuleset drawableRuleset = new TestDrawableRuleset(); @@ -137,7 +136,6 @@ 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()) { diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 489a7ff721..3e3240c683 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -76,7 +76,6 @@ namespace osu.Game.Screens.Play.HUD private void onJudgementChanged(JudgementResult judgement) { - if (gameplayState == null) { isValid = false; @@ -98,7 +97,6 @@ namespace osu.Game.Screens.Play.HUD return count.ToLocalisableString("0.00 UR"); } - protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => { @@ -115,7 +113,5 @@ namespace osu.Game.Screens.Play.HUD loadCancellationSource?.Cancel(); } - - } } From 8923561b0543cd2881ba55c56beef215b3d11bed Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Thu, 4 Nov 2021 14:27:48 -0600 Subject: [PATCH 05/26] Removed Unnessicary class in the Test file, and optimized UR counter --- .../Gameplay/TestSceneUnstableRateCounter.cs | 94 ++----------- .../Screens/Play/HUD/UnstableRateCounter.cs | 133 +++++++++++++----- 2 files changed, 109 insertions(+), 118 deletions(-) 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 }, + } + } + }; + } + } } } From 0ffd41bb70a3e57dd729d36559fa9ef2e7c045f8 Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Thu, 4 Nov 2021 14:32:28 -0600 Subject: [PATCH 06/26] Slight logical error in the setValid method --- osu.Game/Screens/Play/HUD/UnstableRateCounter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 401924a898..c68f24c721 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -67,8 +67,9 @@ namespace osu.Game.Screens.Play.HUD private bool isValid; private void setValid(bool valid) { - isValid = valid; + if (isValid == valid) return; DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); + isValid = valid; } private void onJudgementAdded(JudgementResult judgement) From 8bfcb89221ebd11d919cafd04b14d9cb05a8bafc Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Thu, 4 Nov 2021 21:24:25 -0600 Subject: [PATCH 07/26] Fixed issue where UR counter was always invalid --- osu.Game/Screens/Play/HUD/UnstableRateCounter.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index c68f24c721..0045dab004 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -21,7 +21,6 @@ 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 { @@ -64,11 +63,11 @@ namespace osu.Game.Screens.Play.HUD } } - private bool isValid; + private bool isValid = false; private void setValid(bool valid) { if (isValid == valid) return; - DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); + DrawableCount.FadeTo(valid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); isValid = valid; } @@ -81,7 +80,7 @@ namespace osu.Game.Screens.Play.HUD updateUR(); } - // Only populate via the score if the user has moved the current location. + // Only populate via the score if the user has moved the current location. private void onJudgementChanged(JudgementResult judgement) { ScoreInfo currentScore = new ScoreInfo(); @@ -93,7 +92,7 @@ namespace osu.Game.Screens.Play.HUD private void updateUR() { - if (hitList.Count > 0) + if (hitList.Count > 1) { double mean = hitList.Average(); double squares = hitList.Select(offset => Math.Pow(offset - mean, 2)).Sum(); @@ -152,7 +151,6 @@ namespace osu.Game.Screens.Play.HUD InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, - //Spacing = new Vector2(2), Children = new Drawable[] { intPart = new OsuSpriteText @@ -165,7 +163,6 @@ namespace osu.Game.Screens.Play.HUD { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Text = @" UR", Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), Padding = new MarginPadding { Bottom = 1.5f }, } From 77e853ce2593f9973b89f4386592a12fda972e1f Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Fri, 5 Nov 2021 12:16:58 -0600 Subject: [PATCH 08/26] Optimized UR Counter and removed redundant code --- .../Gameplay/TestSceneUnstableRateCounter.cs | 90 +++++++------------ .../Screens/Play/HUD/UnstableRateCounter.cs | 83 +++++++++-------- 2 files changed, 79 insertions(+), 94 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index be1cd8a35c..0dc25cf378 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -1,21 +1,14 @@ // 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.Diagnostics.CodeAnalysis; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osuTK; @@ -26,23 +19,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(ScoreProcessor))] private TestScoreProcessor scoreProcessor = new TestScoreProcessor(); - [Cached(typeof(GameplayState))] - private GameplayState gameplayState; + private readonly OsuHitWindows hitWindows = new OsuHitWindows(); - private OsuHitWindows hitWindows = new OsuHitWindows(); + private UnstableRateCounter counter; private double prev; - public TestSceneUnstableRateCounter() - { - Score score = new Score - { - ScoreInfo = new ScoreInfo(), - }; - gameplayState = new GameplayState(null, null, null, score); - scoreProcessor.NewJudgement += result => scoreProcessor.PopulateScore(score.ScoreInfo); - } - [SetUpSteps] public void SetUp() { @@ -52,41 +34,38 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestBasic() { - AddStep("Create Display", () => recreateDisplay()); + AddStep("Create Display", recreateDisplay); - AddRepeatStep("Set UR to 250", () => setUR(25), 20); + // Needs multiples 2 by the nature of UR, and went for 4 to be safe. + // Creates a 250 UR by placing a +25ms then a -25ms judgement, which then results in a 250 UR + AddRepeatStep("Set UR to 250", () => applyJudgement(25, true), 4); - AddStep("Reset UR", () => + AddUntilStep("UR = 250", () => counter.Current.Value == 250.0); + + AddRepeatStep("Revert UR", () => { - scoreProcessor.Reset(); - recreateDisplay(); - }); + scoreProcessor.RevertResult( + new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) + { + TimeOffset = 25, + Type = HitResult.Perfect, + }); + }, 4); - AddRepeatStep("Set UR to 100", () => setUR(10), 20); + AddUntilStep("UR is 0", () => counter.Current.Value == 0.0); + AddUntilStep("Counter is invalid", () => counter.Child.Alpha == 0.3f); - AddStep("Reset UR", () => - { - scoreProcessor.Reset(); - recreateDisplay(); - }); - - AddRepeatStep("Set UR to 0 (+50ms offset)", () => newJudgement(50), 10); - - AddStep("Reset UR", () => - { - scoreProcessor.Reset(); - recreateDisplay(); - }); - - AddRepeatStep("Set UR to 0 (-50 offset)", () => newJudgement(-50), 10); - - AddRepeatStep("Random Judgements", () => newJudgement(), 20); + //Sets a UR of 0 by creating 10 10ms offset judgements. Since average = offset, UR = 0 + AddRepeatStep("Set UR to 0", () => applyJudgement(10, false), 10); + //Applies a UR of 100 by creating 10 -10ms offset judgements. At the 10th judgement, offset should be 100. + AddRepeatStep("Bring UR to 100", () => applyJudgement(-10, false), 10); } + private void recreateDisplay() { Clear(); - Add(new UnstableRateCounter + Add(counter = new UnstableRateCounter { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -94,24 +73,21 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private void newJudgement(double offset = 0, HitResult result = HitResult.Perfect) + private void applyJudgement(double offsetMs, bool alt) { - scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) - { - TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, - Type = result, - }); - } + double placement = offsetMs; + + if (alt) + { + placement = prev > 0 ? -offsetMs : offsetMs; + prev = placement; + } - private void setUR(double UR = 0, HitResult result = HitResult.Perfect) - { - double placement = prev > 0 ? -UR : UR; scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) { TimeOffset = placement, - Type = result, + Type = HitResult.Perfect, }); - prev = placement; } private class TestScoreProcessor : ScoreProcessor diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 0045dab004..7e6e3d9b39 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -4,10 +4,8 @@ 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; @@ -18,8 +16,6 @@ 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; namespace osu.Game.Screens.Play.HUD @@ -28,19 +24,17 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - protected override bool IsRollingProportional => true; - protected override double RollingDuration => 750; private const float alpha_when_invalid = 0.3f; - private List hitList = new List(); + private readonly List hitOffsets = new List(); + //May be able to remove the CanBeNull as ScoreProcessor should exist everywhere, for example, in the skin editor it is cached. [CanBeNull] [Resolved(CanBeNull = true)] private ScoreProcessor scoreProcessor { get; set; } - private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); public UnstableRateCounter() { Current.Value = 0.0; @@ -56,17 +50,18 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - if (scoreProcessor != null) - { - scoreProcessor.NewJudgement += onJudgementAdded; - scoreProcessor.JudgementReverted += onJudgementChanged; - } + if (scoreProcessor == null) return; + + scoreProcessor.NewJudgement += onJudgementAdded; + scoreProcessor.JudgementReverted += onJudgementReverted; } - private bool isValid = false; + private bool isValid; + private void setValid(bool valid) { if (isValid == valid) return; + DrawableCount.FadeTo(valid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); isValid = valid; } @@ -75,39 +70,46 @@ namespace osu.Game.Screens.Play.HUD { if (!(judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows) && judgement.IsHit) { - hitList.Add(judgement.TimeOffset); + hitOffsets.Add(judgement.TimeOffset); } - updateUR(); + + updateUr(); } - // Only populate via the score if the user has moved the current location. - private void onJudgementChanged(JudgementResult judgement) + // If a judgement was reverted successfully, remove the item from the hitOffsets list. + private void onJudgementReverted(JudgementResult judgement) { - 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(); + //Score Processor Conditions to revert + if (judgement.FailedAtJudgement || !judgement.Type.IsScorable()) + return; + //UR Conditions to Revert + if (judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows || !judgement.IsHit) + return; + + hitOffsets.RemoveAt(hitOffsets.Count - 1); + updateUr(); } - private void updateUR() + private void updateUr() { - if (hitList.Count > 1) + // At Count = 0, we get NaN, While we are allowing count = 1, it will be 0 since average = offset. + if (hitOffsets.Count > 0) { - double mean = hitList.Average(); - double squares = hitList.Select(offset => Math.Pow(offset - mean, 2)).Sum(); - Current.Value = Math.Sqrt(squares / hitList.Count) * 10; + double mean = hitOffsets.Average(); + double squares = hitOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); + Current.Value = Math.Sqrt(squares / hitOffsets.Count) * 10; setValid(true); } else { + Current.Value = 0; setValid(false); } } protected override LocalisableString FormatCount(double count) { - return count.ToString("0.00"); + return count.ToString("0.00 UR"); } protected override IHasText CreateText() => new TextComponent @@ -119,12 +121,10 @@ namespace osu.Game.Screens.Play.HUD { base.Dispose(isDisposing); - if (scoreProcessor != null) - { - scoreProcessor.NewJudgement -= onJudgementAdded; - scoreProcessor.JudgementReverted -= onJudgementChanged; - } - loadCancellationSource?.Cancel(); + if (scoreProcessor == null) return; + + scoreProcessor.NewJudgement -= onJudgementAdded; + scoreProcessor.JudgementReverted -= onJudgementReverted; } private class TextComponent : CompositeDrawable, IHasText @@ -132,11 +132,12 @@ namespace osu.Game.Screens.Play.HUD public LocalisableString Text { get => intPart.Text; - set { + 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"; + decimalPart.Text = splitValue[1]; } } @@ -159,6 +160,14 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.BottomLeft, Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), + Text = ".", + Padding = new MarginPadding { Bottom = 1.5f }, + }, decimalPart = new OsuSpriteText { Anchor = Anchor.BottomLeft, From cc0bcf6b2c2187a8a5adaeb5e360f2a4d6705569 Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Sun, 7 Nov 2021 17:44:50 -0700 Subject: [PATCH 09/26] Clean up judgement checks and clean up setter/getter for UR counter Text --- .../Screens/Play/HUD/UnstableRateCounter.cs | 52 +++++++------------ 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 7e6e3d9b39..a9d6066e09 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -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; @@ -30,9 +30,7 @@ namespace osu.Game.Screens.Play.HUD private readonly List hitOffsets = new List(); - //May be able to remove the CanBeNull as ScoreProcessor should exist everywhere, for example, in the skin editor it is cached. - [CanBeNull] - [Resolved(CanBeNull = true)] + [Resolved] private ScoreProcessor scoreProcessor { get; set; } public UnstableRateCounter() @@ -46,12 +44,15 @@ namespace osu.Game.Screens.Play.HUD Colour = colours.BlueLighter; } + private bool isUrInvalid(JudgementResult judgement) + { + return judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows || !judgement.IsHit; + } + protected override void LoadComplete() { base.LoadComplete(); - if (scoreProcessor == null) return; - scoreProcessor.NewJudgement += onJudgementAdded; scoreProcessor.JudgementReverted += onJudgementReverted; } @@ -68,23 +69,15 @@ namespace osu.Game.Screens.Play.HUD private void onJudgementAdded(JudgementResult judgement) { - if (!(judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows) && judgement.IsHit) - { - hitOffsets.Add(judgement.TimeOffset); - } + if (isUrInvalid(judgement)) return; + hitOffsets.Add(judgement.TimeOffset); updateUr(); } - // If a judgement was reverted successfully, remove the item from the hitOffsets list. private void onJudgementReverted(JudgementResult judgement) { - //Score Processor Conditions to revert - if (judgement.FailedAtJudgement || !judgement.Type.IsScorable()) - return; - //UR Conditions to Revert - if (judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows || !judgement.IsHit) - return; + if (judgement.FailedAtJudgement || isUrInvalid(judgement)) return; hitOffsets.RemoveAt(hitOffsets.Count - 1); updateUr(); @@ -109,7 +102,7 @@ namespace osu.Game.Screens.Play.HUD protected override LocalisableString FormatCount(double count) { - return count.ToString("0.00 UR"); + return count.ToString("0.00"); } protected override IHasText CreateText() => new TextComponent @@ -121,8 +114,6 @@ namespace osu.Game.Screens.Play.HUD { base.Dispose(isDisposing); - if (scoreProcessor == null) return; - scoreProcessor.NewJudgement -= onJudgementAdded; scoreProcessor.JudgementReverted -= onJudgementReverted; } @@ -131,17 +122,18 @@ namespace osu.Game.Screens.Play.HUD { public LocalisableString Text { - get => intPart.Text; + get => fullValue.ToLocalisableString("0.00 UR"); 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]; + fullValue = Convert.ToDouble(value.ToString()); + intPart.Text = fullValue.ToLocalisableString("0"); + decimalPart.Text = (fullValue - Math.Truncate(fullValue)) + .ToLocalisableString(".00 UR"); } } - private string[] splitValue; + private double fullValue; + private readonly OsuSpriteText intPart; private readonly OsuSpriteText decimalPart; @@ -160,14 +152,6 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.BottomLeft, Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), - Text = ".", - Padding = new MarginPadding { Bottom = 1.5f }, - }, decimalPart = new OsuSpriteText { Anchor = Anchor.BottomLeft, From a8c9ad73c1bc1bcf9da155b5a14b09c5146803fe Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Sun, 7 Nov 2021 18:06:13 -0700 Subject: [PATCH 10/26] Make UR Counter isValid into a bindable boolean --- .../Screens/Play/HUD/UnstableRateCounter.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index a9d6066e09..f7263b29e9 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -27,6 +28,7 @@ namespace osu.Game.Screens.Play.HUD protected override double RollingDuration => 750; private const float alpha_when_invalid = 0.3f; + private readonly Bindable valid = new Bindable(); private readonly List hitOffsets = new List(); @@ -42,6 +44,8 @@ namespace osu.Game.Screens.Play.HUD private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) { Colour = colours.BlueLighter; + valid.BindValueChanged(e => + DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); } private bool isUrInvalid(JudgementResult judgement) @@ -57,16 +61,6 @@ namespace osu.Game.Screens.Play.HUD scoreProcessor.JudgementReverted += onJudgementReverted; } - private bool isValid; - - private void setValid(bool valid) - { - if (isValid == valid) return; - - DrawableCount.FadeTo(valid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); - isValid = valid; - } - private void onJudgementAdded(JudgementResult judgement) { if (isUrInvalid(judgement)) return; @@ -91,12 +85,12 @@ namespace osu.Game.Screens.Play.HUD double mean = hitOffsets.Average(); double squares = hitOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); Current.Value = Math.Sqrt(squares / hitOffsets.Count) * 10; - setValid(true); + valid.Value = true; } else { Current.Value = 0; - setValid(false); + valid.Value = false; } } From 0faa26fc13905d13f67cfef7da0fca420382f6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 17 Oct 2021 21:34:31 +0200 Subject: [PATCH 11/26] Add basic structure for buttons --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 34 ++++++++++++-- .../Cards/Buttons/BeatmapCardIconButton.cs | 46 +++++++++++++++++++ .../Drawables/Cards/Buttons/DownloadButton.cs | 22 +++++++++ .../Cards/Buttons/FavouriteButton.cs | 22 +++++++++ 4 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index c53c1abd8d..a3650315cc 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -21,6 +22,7 @@ using osuTK; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; +using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton; namespace osu.Game.Beatmaps.Drawables.Cards { @@ -35,7 +37,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly APIBeatmapSet beatmapSet; private UpdateableOnlineBeatmapSetCover leftCover; - private FillFlowContainer iconArea; + private FillFlowContainer leftIconArea; + + private Container rightButtonArea; private Container mainContent; private BeatmapCardContentBackground mainContentBackground; @@ -79,7 +83,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Both, OnlineInfo = beatmapSet }, - iconArea = new FillFlowContainer + leftIconArea = new FillFlowContainer { Margin = new MarginPadding(5), AutoSizeAxes = Axes.Both, @@ -88,6 +92,27 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } }, + rightButtonArea = new Container + { + Name = @"Right (button) area", + Width = 30, + RelativeSizeAxes = Axes.Y, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 14), + Children = new BeatmapCardIconButton[] + { + new FavouriteButton(beatmapSet), + new DownloadButton(beatmapSet) + } + } + }, mainContent = new Container { Name = @"Main content", @@ -226,10 +251,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards }; if (beatmapSet.HasVideo) - iconArea.Add(new IconPill(FontAwesome.Solid.Film)); + leftIconArea.Add(new IconPill(FontAwesome.Solid.Film)); if (beatmapSet.HasStoryboard) - iconArea.Add(new IconPill(FontAwesome.Solid.Image)); + leftIconArea.Add(new IconPill(FontAwesome.Solid.Image)); if (beatmapSet.HasExplicitContent) { @@ -306,6 +331,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint); statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + rightButtonArea.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs new file mode 100644 index 0000000000..155259d859 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs @@ -0,0 +1,46 @@ +// 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.Sprites; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Beatmaps.Drawables.Cards.Buttons +{ + public abstract class BeatmapCardIconButton : OsuHoverContainer + { + protected readonly SpriteIcon Icon; + + private float size; + + public new float Size + { + get => size; + set + { + size = value; + Icon.Size = new Vector2(size); + } + } + + protected BeatmapCardIconButton() + { + Add(Icon = new SpriteIcon()); + + AutoSizeAxes = Axes.Both; + Size = 12; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Anchor = Origin = Anchor.Centre; + + IdleColour = colourProvider.Light1; + HoverColour = colourProvider.Content1; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs new file mode 100644 index 0000000000..da6fe3c8be --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -0,0 +1,22 @@ +// 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.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Beatmaps.Drawables.Cards.Buttons +{ + public class DownloadButton : BeatmapCardIconButton + { + private readonly APIBeatmapSet beatmapSet; + + public DownloadButton(APIBeatmapSet beatmapSet) + { + this.beatmapSet = beatmapSet; + + Icon.Icon = FontAwesome.Solid.FileDownload; + } + + // TODO: implement behaviour + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs new file mode 100644 index 0000000000..1859a66821 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -0,0 +1,22 @@ +// 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.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Beatmaps.Drawables.Cards.Buttons +{ + public class FavouriteButton : BeatmapCardIconButton + { + private readonly APIBeatmapSet beatmapSet; + + public FavouriteButton(APIBeatmapSet beatmapSet) + { + this.beatmapSet = beatmapSet; + + Icon.Icon = FontAwesome.Regular.Heart; + } + + // TODO: implement behaviour + } +} From 5cb533004de0329b90c4428f4cf1311db51529d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Oct 2021 23:11:31 +0200 Subject: [PATCH 12/26] Add test coverage for favourite button --- .../TestSceneBeatmapCardFavouriteButton.cs | 86 +++++++++++++++++++ .../Requests/PostBeatmapFavouriteRequest.cs | 7 +- 2 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardFavouriteButton.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardFavouriteButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardFavouriteButton.cs new file mode 100644 index 0000000000..77c9debef6 --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardFavouriteButton.cs @@ -0,0 +1,86 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public class TestSceneBeatmapCardFavouriteButton : OsuManualInputManagerTestScene + { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Test] + public void TestInitialState([Values] bool favourited) + { + APIBeatmapSet beatmapSetInfo = null; + FavouriteButton button = null; + + AddStep("create beatmap set", () => + { + beatmapSetInfo = CreateAPIBeatmapSet(Ruleset.Value); + beatmapSetInfo.HasFavourited = favourited; + }); + AddStep("create button", () => Child = button = new FavouriteButton(beatmapSetInfo) { Scale = new Vector2(2) }); + + assertCorrectIcon(favourited); + AddAssert("correct tooltip text", () => button.TooltipText == (favourited ? BeatmapsetsStrings.ShowDetailsUnfavourite : BeatmapsetsStrings.ShowDetailsFavourite)); + } + + [Test] + public void TestRequestHandling() + { + APIBeatmapSet beatmapSetInfo = null; + FavouriteButton button = null; + BeatmapFavouriteAction? lastRequestAction = null; + + AddStep("create beatmap set", () => beatmapSetInfo = CreateAPIBeatmapSet(Ruleset.Value)); + AddStep("create button", () => Child = button = new FavouriteButton(beatmapSetInfo) { Scale = new Vector2(2) }); + + assertCorrectIcon(false); + + AddStep("register request handling", () => dummyAPI.HandleRequest = request => + { + if (!(request is PostBeatmapFavouriteRequest favouriteRequest)) + return false; + + lastRequestAction = favouriteRequest.Action; + request.TriggerSuccess(); + return true; + }); + + AddStep("click icon", () => + { + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("favourite request sent", () => lastRequestAction == BeatmapFavouriteAction.Favourite); + assertCorrectIcon(true); + + AddStep("click icon", () => + { + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("unfavourite request sent", () => lastRequestAction == BeatmapFavouriteAction.UnFavourite); + assertCorrectIcon(false); + } + + private void assertCorrectIcon(bool favourited) => AddAssert("icon correct", + () => this.ChildrenOfType().Single().Icon.Equals(favourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart)); + } +} diff --git a/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs b/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs index f3724230cb..9fdc3382aa 100644 --- a/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs +++ b/osu.Game/Online/API/Requests/PostBeatmapFavouriteRequest.cs @@ -8,20 +8,21 @@ namespace osu.Game.Online.API.Requests { public class PostBeatmapFavouriteRequest : APIRequest { + public readonly BeatmapFavouriteAction Action; + private readonly int id; - private readonly BeatmapFavouriteAction action; public PostBeatmapFavouriteRequest(int id, BeatmapFavouriteAction action) { this.id = id; - this.action = action; + Action = action; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; - req.AddParameter(@"action", action.ToString().ToLowerInvariant()); + req.AddParameter(@"action", Action.ToString().ToLowerInvariant()); return req; } From b5cbdcf9819dff87a57758b4577c80da49f9ed85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Oct 2021 23:36:27 +0200 Subject: [PATCH 13/26] Implement basic behaviour of favourite button --- .../Cards/Buttons/FavouriteButton.cs | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs index 1859a66821..145b3f41b0 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -1,8 +1,13 @@ // 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.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Logging; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { @@ -10,13 +15,54 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { private readonly APIBeatmapSet beatmapSet; + private PostBeatmapFavouriteRequest favouriteRequest; + public FavouriteButton(APIBeatmapSet beatmapSet) { this.beatmapSet = beatmapSet; - Icon.Icon = FontAwesome.Regular.Heart; + updateState(); } - // TODO: implement behaviour + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + Action = () => + { + var actionType = beatmapSet.HasFavourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; + + favouriteRequest?.Cancel(); + favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType); + + Enabled.Value = false; + favouriteRequest.Success += () => + { + beatmapSet.HasFavourited = actionType == BeatmapFavouriteAction.Favourite; + Enabled.Value = true; + updateState(); + }; + favouriteRequest.Failure += e => + { + Logger.Error(e, $"Failed to {actionType.ToString().ToLower()} beatmap: {e.Message}"); + Enabled.Value = true; + }; + + api.Queue(favouriteRequest); + }; + } + + private void updateState() + { + if (beatmapSet.HasFavourited) + { + Icon.Icon = FontAwesome.Solid.Heart; + TooltipText = BeatmapsetsStrings.ShowDetailsUnfavourite; + } + else + { + Icon.Icon = FontAwesome.Regular.Heart; + TooltipText = BeatmapsetsStrings.ShowDetailsFavourite; + } + } } } From f4b8dee2d0562d931faf6a96f6326882f3190df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 Nov 2021 17:42:32 +0100 Subject: [PATCH 14/26] Update favourite statistic value on favourite button clicks --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 18 +++++++++ .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 7 +++- .../Cards/BeatmapSetFavouriteState.cs | 31 ++++++++++++++++ .../Cards/Buttons/FavouriteButton.cs | 37 ++++++++++++++----- .../Cards/Statistics/FavouritesStatistic.cs | 29 +++++++++++++-- 5 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/BeatmapSetFavouriteState.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 8bfee02310..0feaa8f480 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -9,8 +9,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osuTK; @@ -20,6 +23,8 @@ namespace osu.Game.Tests.Visual.Beatmaps { public class TestSceneBeatmapCard : OsuTestScene { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + private APIBeatmapSet[] testCases; #region Test case generation @@ -164,6 +169,19 @@ namespace osu.Game.Tests.Visual.Beatmaps #endregion + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => dummyAPI.HandleRequest = request => + { + if (!(request is PostBeatmapFavouriteRequest)) + return false; + + request.TriggerSuccess(); + return true; + }); + } + private Drawable createContent(OverlayColourScheme colourScheme, Func creationFunc) { var colourProvider = new OverlayColourProvider(colourScheme); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index a3650315cc..71376c28f1 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -35,6 +36,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private const float corner_radius = 10; private readonly APIBeatmapSet beatmapSet; + private readonly Bindable favouriteState; private UpdateableOnlineBeatmapSetCover leftCover; private FillFlowContainer leftIconArea; @@ -55,6 +57,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards : base(HoverSampleSet.Submit) { this.beatmapSet = beatmapSet; + favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); } [BackgroundDependencyLoader] @@ -108,7 +111,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards Spacing = new Vector2(0, 14), Children = new BeatmapCardIconButton[] { - new FavouriteButton(beatmapSet), + new FavouriteButton(beatmapSet) { Current = favouriteState }, new DownloadButton(beatmapSet) } } @@ -312,7 +315,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards if (beatmapSet.HypeStatus != null && beatmapSet.NominationStatus != null) yield return new NominationsStatistic(beatmapSet.NominationStatus); - yield return new FavouritesStatistic(beatmapSet); + yield return new FavouritesStatistic(beatmapSet) { Current = favouriteState }; yield return new PlayCountStatistic(beatmapSet); var dateStatistic = BeatmapCardDateStatistic.CreateFor(beatmapSet); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapSetFavouriteState.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapSetFavouriteState.cs new file mode 100644 index 0000000000..82523cc865 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapSetFavouriteState.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Beatmaps.Drawables.Cards.Statistics; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + /// + /// Stores the current favourite state of a beatmap set. + /// Used to coordinate between and . + /// + public readonly struct BeatmapSetFavouriteState + { + /// + /// Whether the currently logged-in user has favourited this beatmap. + /// + public bool Favourited { get; } + + /// + /// The number of favourites that the beatmap set has received, including the currently logged-in user. + /// + public int FavouriteCount { get; } + + public BeatmapSetFavouriteState(bool favourited, int favouriteCount) + { + Favourited = favourited; + FavouriteCount = favouriteCount; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs index 145b3f41b0..0b43b1c619 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Framework.Logging; using osu.Game.Online.API; @@ -11,17 +13,24 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { - public class FavouriteButton : BeatmapCardIconButton + public class FavouriteButton : BeatmapCardIconButton, IHasCurrentValue { - private readonly APIBeatmapSet beatmapSet; + private readonly BindableWithCurrent current; + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly int onlineBeatmapID; private PostBeatmapFavouriteRequest favouriteRequest; public FavouriteButton(APIBeatmapSet beatmapSet) { - this.beatmapSet = beatmapSet; - - updateState(); + current = new BindableWithCurrent(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); + onlineBeatmapID = beatmapSet.OnlineID; } [BackgroundDependencyLoader] @@ -29,17 +38,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { Action = () => { - var actionType = beatmapSet.HasFavourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; + var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; favouriteRequest?.Cancel(); - favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType); + favouriteRequest = new PostBeatmapFavouriteRequest(onlineBeatmapID, actionType); Enabled.Value = false; favouriteRequest.Success += () => { - beatmapSet.HasFavourited = actionType == BeatmapFavouriteAction.Favourite; + bool favourited = actionType == BeatmapFavouriteAction.Favourite; + + current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1)); + Enabled.Value = true; - updateState(); }; favouriteRequest.Failure += e => { @@ -51,9 +62,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons }; } + protected override void LoadComplete() + { + base.LoadComplete(); + current.BindValueChanged(_ => updateState(), true); + } + private void updateState() { - if (beatmapSet.HasFavourited) + if (current.Value.Favourited) { Icon.Icon = FontAwesome.Solid.Heart; TooltipText = BeatmapsetsStrings.ShowDetailsUnfavourite; diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs index 7b3286ddcc..d924fd938b 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using Humanizer; +using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Beatmaps.Drawables.Cards.Statistics @@ -11,13 +13,32 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics /// /// Shows the number of favourites that a beatmap set has received. /// - public class FavouritesStatistic : BeatmapCardStatistic + public class FavouritesStatistic : BeatmapCardStatistic, IHasCurrentValue { + private readonly BindableWithCurrent current; + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + public FavouritesStatistic(IBeatmapSetOnlineInfo onlineInfo) { - Icon = onlineInfo.HasFavourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; - Text = onlineInfo.FavouriteCount.ToMetric(decimals: 1); - TooltipText = BeatmapsStrings.PanelFavourites(onlineInfo.FavouriteCount.ToLocalisableString(@"N0")); + current = new BindableWithCurrent(new BeatmapSetFavouriteState(onlineInfo.HasFavourited, onlineInfo.FavouriteCount)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + current.BindValueChanged(_ => updateState(), true); + } + + private void updateState() + { + Icon = current.Value.Favourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; + Text = current.Value.FavouriteCount.ToMetric(decimals: 1); + TooltipText = BeatmapsStrings.PanelFavourites(current.Value.FavouriteCount.ToLocalisableString(@"N0")); } } } From d88fd1bfd7a71d43ec907a66fb4038f9d1c36222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Nov 2021 13:23:24 +0100 Subject: [PATCH 15/26] Add failing test case --- .../Editor/TestSceneEditorSaving.cs | 91 +++++++++++++++++++ .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs new file mode 100644 index 0000000000..159a64d1ac --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Input; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Setup; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Select; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Taiko.Tests.Editor +{ + public class TestSceneEditorSaving : OsuGameTestScene + { + private Screens.Edit.Editor editor => Game.ChildrenOfType().FirstOrDefault(); + + private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap)); + + /// + /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. + /// Emphasis is placed on , since taiko has special handling for it to keep compatibility with stable. + /// + [Test] + public void TestNewBeatmapSaveThenLoad() + { + AddStep("set default beatmap", () => Game.Beatmap.SetDefault()); + AddStep("set taiko ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); + + PushAndConfirm(() => new EditorLoader()); + + AddUntilStep("wait for editor load", () => editor?.IsLoaded == true); + + AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + + // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten. + + AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); + AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + + AddStep("Set slider multiplier", () => editorBeatmap.Difficulty.SliderMultiplier = 2); + AddStep("Set artist and title", () => + { + editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; + editorBeatmap.BeatmapInfo.Metadata.Title = "title"; + }); + AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.Version = "difficulty"); + + checkMutations(); + + AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); + + checkMutations(); + + AddStep("Exit", () => InputManager.Key(Key.Escape)); + + AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); + + PushAndConfirm(() => new PlaySongSelect()); + + AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault); + AddStep("Open options", () => InputManager.Key(Key.F3)); + AddStep("Enter editor", () => InputManager.Key(Key.Number5)); + + AddUntilStep("Wait for editor load", () => editor != null); + + checkMutations(); + } + + private void checkMutations() + { + AddAssert("Beatmap has correct slider multiplier", () => + { + // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use. + // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct. + var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty(); + taikoDifficulty.CopyFrom(editorBeatmap.Difficulty); + return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2); + }); + AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title"); + AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.Version == "difficulty"); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 94dfb67d93..dc0edd92ea 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -191,7 +191,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps protected override Beatmap CreateBeatmap() => new TaikoBeatmap(); - private class TaikoMultiplierAppliedDifficulty : BeatmapDifficulty + internal class TaikoMultiplierAppliedDifficulty : BeatmapDifficulty { public TaikoMultiplierAppliedDifficulty(IBeatmapDifficultyInfo difficulty) { From 540b7e1b386c149940847b6583c33dfd63f97974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Nov 2021 13:23:54 +0100 Subject: [PATCH 16/26] Fix taiko editor not unapplying slider multiplier changes on save --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game/Beatmaps/BeatmapModelManager.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index dc0edd92ea..9b2e9fedc5 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { base.CopyTo(other); if (!(other is TaikoMultiplierAppliedDifficulty)) - SliderMultiplier /= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + other.SliderMultiplier /= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; } public override void CopyFrom(IBeatmapDifficultyInfo other) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index eb1bf598a4..a654b05edb 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -189,7 +189,11 @@ namespace osu.Game.Beatmaps // Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`. // This should hopefully be temporary, assuming said clone is eventually removed. - beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty); + + // Warning: The directionality here is important. Changes have to be copied *from* beatmapContent (which comes from editor and is being saved) + // *to* the beatmapInfo (which is a database model and needs to receive values without the taiko slider velocity multiplier for correct operation). + // CopyTo() will undo such adjustments, while CopyFrom() will not. + beatmapContent.Difficulty.CopyTo(beatmapInfo.BaseDifficulty); // All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding. beatmapContent.BeatmapInfo = beatmapInfo; From 9a2425f316903a8725da40f6d0374afa91aebe8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 21:36:28 +0900 Subject: [PATCH 17/26] Remove unused field for now to appease inspectcode --- osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index da6fe3c8be..00c0ccc3ce 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -8,12 +8,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public class DownloadButton : BeatmapCardIconButton { - private readonly APIBeatmapSet beatmapSet; - public DownloadButton(APIBeatmapSet beatmapSet) { - this.beatmapSet = beatmapSet; - Icon.Icon = FontAwesome.Solid.FileDownload; } From 72489b32f922bc38f5306e5556411f32c5c582d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 21:39:16 +0900 Subject: [PATCH 18/26] Move toggle code into own method for readability --- .../Cards/Buttons/FavouriteButton.cs | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs index 0b43b1c619..a61065b468 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -27,47 +27,48 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private PostBeatmapFavouriteRequest favouriteRequest; + [Resolved] + private IAPIProvider api { get; set; } + public FavouriteButton(APIBeatmapSet beatmapSet) { current = new BindableWithCurrent(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); onlineBeatmapID = beatmapSet.OnlineID; } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - Action = () => - { - var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; - - favouriteRequest?.Cancel(); - favouriteRequest = new PostBeatmapFavouriteRequest(onlineBeatmapID, actionType); - - Enabled.Value = false; - favouriteRequest.Success += () => - { - bool favourited = actionType == BeatmapFavouriteAction.Favourite; - - current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1)); - - Enabled.Value = true; - }; - favouriteRequest.Failure += e => - { - Logger.Error(e, $"Failed to {actionType.ToString().ToLower()} beatmap: {e.Message}"); - Enabled.Value = true; - }; - - api.Queue(favouriteRequest); - }; - } - protected override void LoadComplete() { base.LoadComplete(); + + Action = toggleFavouriteStatus; current.BindValueChanged(_ => updateState(), true); } + private void toggleFavouriteStatus() + { + var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; + + favouriteRequest?.Cancel(); + favouriteRequest = new PostBeatmapFavouriteRequest(onlineBeatmapID, actionType); + + Enabled.Value = false; + favouriteRequest.Success += () => + { + bool favourited = actionType == BeatmapFavouriteAction.Favourite; + + current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1)); + + Enabled.Value = true; + }; + favouriteRequest.Failure += e => + { + Logger.Error(e, $"Failed to {actionType.ToString().ToLower()} beatmap: {e.Message}"); + Enabled.Value = true; + }; + + api.Queue(favouriteRequest); + } + private void updateState() { if (current.Value.Favourited) From 74603253d2522baeb5342a68ee970c387efdb69a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Nov 2021 13:42:56 +0100 Subject: [PATCH 19/26] Store full model rather than online ID only --- .../Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs index a61065b468..9fed2fde6f 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons set => current.Current = value; } - private readonly int onlineBeatmapID; + private readonly APIBeatmapSet beatmapSet; private PostBeatmapFavouriteRequest favouriteRequest; @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons public FavouriteButton(APIBeatmapSet beatmapSet) { current = new BindableWithCurrent(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); - onlineBeatmapID = beatmapSet.OnlineID; + this.beatmapSet = beatmapSet; } protected override void LoadComplete() @@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; favouriteRequest?.Cancel(); - favouriteRequest = new PostBeatmapFavouriteRequest(onlineBeatmapID, actionType); + favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType); Enabled.Value = false; favouriteRequest.Success += () => From d5ad776c33d88335e3a3a70c853a9d62c34c6d63 Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Mon, 8 Nov 2021 06:27:25 -0700 Subject: [PATCH 20/26] Remove decimal part of UR Counter and rename methods --- .../Screens/Play/HUD/UnstableRateCounter.cs | 50 +++++++------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index f7263b29e9..89ae4f8a21 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -21,7 +20,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class UnstableRateCounter : RollingCounter, ISkinnableDrawable + public class UnstableRateCounter : RollingCounter, ISkinnableDrawable { public bool UsesFixedAnchor { get; set; } @@ -37,7 +36,7 @@ namespace osu.Game.Screens.Play.HUD public UnstableRateCounter() { - Current.Value = 0.0; + Current.Value = 0; } [BackgroundDependencyLoader] @@ -48,10 +47,8 @@ namespace osu.Game.Screens.Play.HUD DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); } - private bool isUrInvalid(JudgementResult judgement) - { - return judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows || !judgement.IsHit; - } + private bool changesUnstableRate(JudgementResult judgement) + => !(judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows) && judgement.IsHit; protected override void LoadComplete() { @@ -63,28 +60,28 @@ namespace osu.Game.Screens.Play.HUD private void onJudgementAdded(JudgementResult judgement) { - if (isUrInvalid(judgement)) return; + if (!changesUnstableRate(judgement)) return; hitOffsets.Add(judgement.TimeOffset); - updateUr(); + updateDisplay(); } private void onJudgementReverted(JudgementResult judgement) { - if (judgement.FailedAtJudgement || isUrInvalid(judgement)) return; + if (judgement.FailedAtJudgement || !changesUnstableRate(judgement)) return; hitOffsets.RemoveAt(hitOffsets.Count - 1); - updateUr(); + updateDisplay(); } - private void updateUr() + private void updateDisplay() { // At Count = 0, we get NaN, While we are allowing count = 1, it will be 0 since average = offset. if (hitOffsets.Count > 0) { double mean = hitOffsets.Average(); double squares = hitOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); - Current.Value = Math.Sqrt(squares / hitOffsets.Count) * 10; + Current.Value = (int)(Math.Sqrt(squares / hitOffsets.Count) * 10); valid.Value = true; } else @@ -94,11 +91,6 @@ namespace osu.Game.Screens.Play.HUD } } - protected override LocalisableString FormatCount(double count) - { - return count.ToString("0.00"); - } - protected override IHasText CreateText() => new TextComponent { Alpha = alpha_when_invalid, @@ -108,6 +100,8 @@ namespace osu.Game.Screens.Play.HUD { base.Dispose(isDisposing); + if (scoreProcessor == null) return; + scoreProcessor.NewJudgement -= onJudgementAdded; scoreProcessor.JudgementReverted -= onJudgementReverted; } @@ -116,20 +110,11 @@ namespace osu.Game.Screens.Play.HUD { public LocalisableString Text { - get => fullValue.ToLocalisableString("0.00 UR"); - set - { - fullValue = Convert.ToDouble(value.ToString()); - intPart.Text = fullValue.ToLocalisableString("0"); - decimalPart.Text = (fullValue - Math.Truncate(fullValue)) - .ToLocalisableString(".00 UR"); - } + get => text.Text; + set => text.Text = value; } - private double fullValue; - - private readonly OsuSpriteText intPart; - private readonly OsuSpriteText decimalPart; + private readonly OsuSpriteText text; public TextComponent() { @@ -140,17 +125,18 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Children = new Drawable[] { - intPart = new OsuSpriteText + text = new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) }, - decimalPart = new OsuSpriteText + new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), + Text = "UR", Padding = new MarginPadding { Bottom = 1.5f }, } } From 8a23b648feb13433847f5a7979563f512f6a4222 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 8 Nov 2021 14:32:00 +0100 Subject: [PATCH 21/26] Move `HoverClickSounds` to the clickable button --- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index e6472dffeb..96a685a9c5 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -153,12 +153,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input new CancelButton { Action = finalise }, new ClearButton { Action = clear }, }, - } + }, + new HoverClickSounds() } } } - }, - new HoverClickSounds() + } }; foreach (var b in bindings) From 1b11a034a75a59bd8bbf7eec441df298457e110e Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Mon, 8 Nov 2021 07:25:02 -0700 Subject: [PATCH 22/26] Reverted Default Skin Changes --- osu.Game/Skinning/DefaultSkin.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index e887ccb2e3..c377f16f8b 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -68,7 +68,6 @@ namespace osu.Game.Skinning var accuracy = container.OfType().FirstOrDefault(); var combo = container.OfType().FirstOrDefault(); var ppCounter = container.OfType().FirstOrDefault(); - var unstableRate = container.OfType().FirstOrDefault(); if (score != null) { @@ -85,17 +84,9 @@ namespace osu.Game.Skinning if (ppCounter != null) { ppCounter.Y = score.Position.Y + ppCounter.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).Y - 4; - ppCounter.X = ppCounter.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding; - ppCounter.Origin = Anchor.TopRight; + ppCounter.Origin = Anchor.TopCentre; ppCounter.Anchor = Anchor.TopCentre; } - if (unstableRate != null) - { - unstableRate.Y = ppCounter.Position.Y; - unstableRate.X = -unstableRate.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding; - unstableRate.Origin = Anchor.TopLeft; - unstableRate.Anchor = Anchor.TopCentre; - } if (accuracy != null) { @@ -139,8 +130,7 @@ namespace osu.Game.Skinning new SongProgress(), new BarHitErrorMeter(), new BarHitErrorMeter(), - new PerformancePointsCounter(), - new UnstableRateCounter() + new PerformancePointsCounter() } }; From 0f129b2cfbdfca268cb6c46272682ec4c2f9d7c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Nov 2021 12:45:00 +0900 Subject: [PATCH 23/26] Disable dependabot for now --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9e9af23b27..814fc81f51 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ updates: schedule: interval: monthly time: "17:00" - open-pull-requests-limit: 99 + open-pull-requests-limit: 0 # disabled until https://github.com/dependabot/dependabot-core/issues/369 is resolved. ignore: - dependency-name: Microsoft.EntityFrameworkCore.Design versions: From 999d625e76fff81cde1de3a62a3ac1ed83db828f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Nov 2021 14:51:06 +0900 Subject: [PATCH 24/26] Fix realm migration potentially failing for users that haven't run osu! in a long time As reported at https://github.com/ppy/osu/discussions/15530. --- osu.Game/Database/RealmContextFactory.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 20667bb3d1..7d9da96160 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -209,7 +209,13 @@ namespace osu.Game.Database case 9: // Pretty pointless to do this as beatmaps aren't really loaded via realm yet, but oh well. - var oldMetadata = migration.OldRealm.DynamicApi.All(getMappedOrOriginalName(typeof(RealmBeatmapMetadata))); + string className = getMappedOrOriginalName(typeof(RealmBeatmapMetadata)); + + // May be coming from a version before `RealmBeatmapMetadata` existed. + if (!migration.OldRealm.Schema.TryFindObjectSchema(className, out _)) + return; + + var oldMetadata = migration.OldRealm.DynamicApi.All(className); var newMetadata = migration.NewRealm.All(); int metadataCount = newMetadata.Count(); From 3b485b5f3700f11396601cd7d49212016d38a276 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Nov 2021 17:27:07 +0900 Subject: [PATCH 25/26] Rewrite `PreviewTrackManager` to avoid constructing `TrackBass` locally This paves the way for the framework code quality change (https://github.com/ppy/osu-framework/pull/4873) which stops exposing the constructor. Most of the restructuring here is required to give `PreviewTrackManager` an adjustable target to apply the global mute. --- .../TestScenePreviewTrackManager.cs | 20 ++++- osu.Game/Audio/PreviewTrackManager.cs | 73 +++---------------- osu.Game/Beatmaps/BeatmapManager.cs | 19 +++-- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 4 +- osu.Game/OsuGameBase.cs | 6 +- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 6 files changed, 46 insertions(+), 78 deletions(-) diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index 89e20043fb..82b6710a17 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -13,10 +13,17 @@ namespace osu.Game.Tests.Visual.Components { public class TestScenePreviewTrackManager : OsuTestScene, IPreviewTrackOwner { - private readonly TestPreviewTrackManager trackManager = new TestPreviewTrackManager(); + private readonly IAdjustableAudioComponent gameTrackAudio = new AudioAdjustments(); + + private readonly TestPreviewTrackManager trackManager; private AudioManager audio; + public TestScenePreviewTrackManager() + { + trackManager = new TestPreviewTrackManager(gameTrackAudio); + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -151,19 +158,19 @@ namespace osu.Game.Tests.Visual.Components audio.VolumeTrack.Value = 1; }); - AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0); + AddAssert("game not muted", () => gameTrackAudio.AggregateVolume.Value != 0); AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack()))); AddUntilStep("wait loaded", () => track.IsLoaded); AddStep("start track", () => track.Start()); - AddAssert("game is muted", () => audio.Tracks.AggregateVolume.Value == 0); + AddAssert("game is muted", () => gameTrackAudio.AggregateVolume.Value == 0); if (stopAnyPlaying) AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner)); else AddStep("stop track", () => track.Stop()); - AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0); + AddAssert("game not muted", () => gameTrackAudio.AggregateVolume.Value != 0); } [Test] @@ -224,6 +231,11 @@ namespace osu.Game.Tests.Visual.Components public new PreviewTrack CurrentTrack => base.CurrentTrack; + public TestPreviewTrackManager(IAdjustableAudioComponent mainTrackAdjustments) + : base(mainTrackAdjustments) + { + } + protected override TrackManagerPreviewTrack CreatePreviewTrack(IBeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore); public override bool UpdateSubTree() diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index ca63add31d..fd9d5a97c6 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -1,13 +1,8 @@ // 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.IO; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Mixing; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -19,27 +14,26 @@ namespace osu.Game.Audio { public class PreviewTrackManager : Component { + private readonly IAdjustableAudioComponent mainTrackAdjustments; + private readonly BindableDouble muteBindable = new BindableDouble(); [Resolved] private AudioManager audio { get; set; } - private PreviewTrackStore trackStore; + private ITrackStore trackStore; protected TrackManagerPreviewTrack CurrentTrack; - private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(OsuGameBase.GLOBAL_TRACK_VOLUME_ADJUST); + public PreviewTrackManager(IAdjustableAudioComponent mainTrackAdjustments) + { + this.mainTrackAdjustments = mainTrackAdjustments; + } [BackgroundDependencyLoader] private void load(AudioManager audioManager) { - // this is a temporary solution to get around muting ourselves. - // todo: update this once we have a BackgroundTrackManager or similar. - trackStore = new PreviewTrackStore(audioManager.TrackMixer, new OnlineStore()); - - audio.AddItem(trackStore); - trackStore.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust); - trackStore.AddAdjustment(AdjustableProperty.Volume, audio.VolumeTrack); + trackStore = audioManager.GetTrackStore(new OnlineStore()); } /// @@ -55,7 +49,7 @@ namespace osu.Game.Audio { CurrentTrack?.Stop(); CurrentTrack = track; - audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); + mainTrackAdjustments.AddAdjustment(AdjustableProperty.Volume, muteBindable); }); track.Stopped += () => Schedule(() => @@ -64,7 +58,7 @@ namespace osu.Game.Audio return; CurrentTrack = null; - audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); + mainTrackAdjustments.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); }); return track; @@ -116,52 +110,5 @@ namespace osu.Game.Audio protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo.OnlineID}.mp3"); } - - private class PreviewTrackStore : AudioCollectionManager, ITrackStore - { - private readonly AudioMixer defaultMixer; - private readonly IResourceStore store; - - internal PreviewTrackStore(AudioMixer defaultMixer, IResourceStore store) - { - this.defaultMixer = defaultMixer; - this.store = store; - } - - public Track GetVirtual(double length = double.PositiveInfinity) - { - if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}"); - - var track = new TrackVirtual(length); - AddItem(track); - return track; - } - - public Track Get(string name) - { - if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}"); - - if (string.IsNullOrEmpty(name)) return null; - - var dataStream = store.GetStream(name); - - if (dataStream == null) - return null; - - // Todo: This is quite unsafe. TrackBass shouldn't be exposed as public. - Track track = new TrackBass(dataStream); - - defaultMixer.Add(track); - AddItem(track); - - return track; - } - - public Task GetAsync(string name) => Task.Run(() => Get(name)); - - public Stream GetStream(string name) => store.GetStream(name); - - public IEnumerable GetAvailableResources() => store.GetAvailableResources(); - } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1e33b54a8f..48a6663adb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -10,6 +10,8 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Audio; +using osu.Framework.Audio.Mixing; +using osu.Framework.Audio.Track; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Testing; @@ -30,18 +32,23 @@ namespace osu.Game.Beatmaps [ExcludeFromDynamicCompile] public class BeatmapManager : IModelDownloader, IModelManager, IModelFileManager, IModelImporter, IWorkingBeatmapCache, IDisposable { + public ITrackStore BeatmapTrackStore { get; } + private readonly BeatmapModelManager beatmapModelManager; private readonly BeatmapModelDownloader beatmapModelDownloader; private readonly WorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue; - public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, - WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore gameResources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false, AudioMixer mainTrackMixer = null) { + var userResources = new FileStore(contextFactory, storage).Store; + + BeatmapTrackStore = audioManager.GetTrackStore(userResources); + beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, api, host); beatmapModelDownloader = CreateBeatmapModelDownloader(beatmapModelManager, api, host); - workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, resources, new FileStore(contextFactory, storage).Store, defaultBeatmap, host); + workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); workingBeatmapCache.BeatmapManager = beatmapModelManager; beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache; @@ -58,8 +65,10 @@ namespace osu.Game.Beatmaps return new BeatmapModelDownloader(modelManager, api, host); } - protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) => - new WorkingBeatmapCache(audioManager, resources, storage, defaultBeatmap, host); + protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) + { + return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); + } protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) => new BeatmapModelManager(storage, contextFactory, rulesets, host); diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 11257e3abc..0af28902a0 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -41,7 +41,7 @@ namespace osu.Game.Beatmaps [CanBeNull] private readonly GameHost host; - public WorkingBeatmapCache([NotNull] AudioManager audioManager, IResourceStore resources, IResourceStore files, WorkingBeatmap defaultBeatmap = null, GameHost host = null) + public WorkingBeatmapCache(ITrackStore trackStore, AudioManager audioManager, IResourceStore resources, IResourceStore files, WorkingBeatmap defaultBeatmap = null, GameHost host = null) { DefaultBeatmap = defaultBeatmap; @@ -50,7 +50,7 @@ namespace osu.Game.Beatmaps this.host = host; this.files = files; largeTextureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(files)); - trackStore = audioManager.GetTrackStore(files); + this.trackStore = trackStore; } public void Invalidate(BeatmapSetInfo info) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 10166daf00..e207d9ce3b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -65,7 +65,7 @@ namespace osu.Game /// /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. /// - internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.8; + private const double global_track_volume_adjust = 0.8; public bool UseDevelopmentServer { get; } @@ -159,7 +159,7 @@ namespace osu.Game private Bindable fpsDisplayVisible; - private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST); + private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(global_track_volume_adjust); private RealmRulesetStore realmRulesetStore; @@ -315,7 +315,7 @@ namespace osu.Game dependencies.Cache(globalBindings); PreviewTrackManager previewTrackManager; - dependencies.Cache(previewTrackManager = new PreviewTrackManager()); + dependencies.Cache(previewTrackManager = new PreviewTrackManager(BeatmapManager.BeatmapTrackStore)); Add(previewTrackManager); AddInternal(MusicController = new MusicController()); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 4b02306d33..07152b5a3e 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual private readonly TestBeatmapManager testBeatmapManager; public TestWorkingBeatmapCache(TestBeatmapManager testBeatmapManager, AudioManager audioManager, IResourceStore resourceStore, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost gameHost) - : base(audioManager, resourceStore, storage, defaultBeatmap, gameHost) + : base(testBeatmapManager.BeatmapTrackStore, audioManager, resourceStore, storage, defaultBeatmap, gameHost) { this.testBeatmapManager = testBeatmapManager; } From dfbc1f33947b2eeb355d8a3a1000ada5668a5041 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Nov 2021 17:42:03 +0900 Subject: [PATCH 26/26] Fix "conflicting" variable name --- osu.Game/Database/RealmContextFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 7d9da96160..42ae986921 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -209,13 +209,13 @@ namespace osu.Game.Database case 9: // Pretty pointless to do this as beatmaps aren't really loaded via realm yet, but oh well. - string className = getMappedOrOriginalName(typeof(RealmBeatmapMetadata)); + string metadataClassName = getMappedOrOriginalName(typeof(RealmBeatmapMetadata)); // May be coming from a version before `RealmBeatmapMetadata` existed. - if (!migration.OldRealm.Schema.TryFindObjectSchema(className, out _)) + if (!migration.OldRealm.Schema.TryFindObjectSchema(metadataClassName, out _)) return; - var oldMetadata = migration.OldRealm.DynamicApi.All(className); + var oldMetadata = migration.OldRealm.DynamicApi.All(metadataClassName); var newMetadata = migration.NewRealm.All(); int metadataCount = newMetadata.Count();