From 8e294c325842430f56189ef8142a4b288279c07f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 13:22:45 +0900 Subject: [PATCH 01/14] Add test coverage of hitting objects immediately after a swell --- .../Judgements/TestSceneSwellJudgements.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs index ccc829f09e..4abad98eab 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs @@ -114,5 +114,75 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements AddAssert("all tick offsets are 0", () => JudgementResults.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0)); } + + /// + /// Ensure input is correctly sent to subsequent hits if a swell is fully completed. + /// + [Test] + public void TestHitSwellThenHitHit() + { + const double swell_time = 1000; + const double hit_time = 1150; + + Swell swell = new Swell + { + StartTime = swell_time, + Duration = 100, + RequiredHits = 1 + }; + + Hit hit = new Hit + { + StartTime = hit_time + }; + + List frames = new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(swell_time, TaikoAction.LeftRim), + new TaikoReplayFrame(hit_time, TaikoAction.RightCentre), + }; + + PerformTest(frames, CreateBeatmap(swell, hit)); + + AssertJudgementCount(3); + + AssertResult(0, HitResult.IgnoreHit); + AssertResult(0, HitResult.LargeBonus); + AssertResult(0, HitResult.Great); + } + + [Test] + public void TestMissSwellThenHitHit() + { + const double swell_time = 1000; + const double hit_time = 1150; + + Swell swell = new Swell + { + StartTime = swell_time, + Duration = 100, + RequiredHits = 1 + }; + + Hit hit = new Hit + { + StartTime = hit_time + }; + + List frames = new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(hit_time, TaikoAction.RightCentre), + }; + + PerformTest(frames, CreateBeatmap(swell, hit)); + + AssertJudgementCount(3); + + AssertResult(0, HitResult.IgnoreMiss); + AssertResult(0, HitResult.IgnoreMiss); + AssertResult(0, HitResult.Great); + } } } From 259ac6d427b3142a99c0366024adb068b97cb47e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 13:16:58 +0900 Subject: [PATCH 02/14] Fix osu!taiko swells eating input after already being judged --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 8441e3a749..3fa6f4b756 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -276,6 +276,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (Time.Current < HitObject.StartTime) return false; + if (AllJudged) + return false; + bool isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre; // Ensure alternating centre and rim hits From f5c472c0fe8a2a2bd9f4d9305be46353119dfce0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 17:36:52 +0900 Subject: [PATCH 03/14] Don't hide mod select overlay when pressing select binding with no search --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 4cb6899ebc..9561a8875c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -542,7 +542,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("clear search", () => modSelectOverlay.SearchTerm = string.Empty); AddStep("press enter", () => InputManager.Key(Key.Enter)); - AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden); + AddAssert("mod select still visible", () => modSelectOverlay.State.Value == Visibility.Visible); } [Test] diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9035503723..c09668850a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -48,7 +48,8 @@ namespace osu.Game.Overlays.Mods /// Contrary to and , the instances /// inside the objects are owned solely by this instance. /// - public Bindable>> AvailableMods { get; } = new Bindable>>(new Dictionary>()); + public Bindable>> AvailableMods { get; } = + new Bindable>>(new Dictionary>()); private Func isValidMod = _ => true; @@ -636,12 +637,9 @@ namespace osu.Game.Overlays.Mods case GlobalAction.Select: { - // Pressing select should select first filtered mod or completely hide the overlay in one shot if search term is empty. + // Pressing select should select first filtered mod if a search is in progress. if (string.IsNullOrEmpty(SearchTerm)) - { - hideOverlay(true); return true; - } ModState? firstMod = columnFlow.Columns.OfType().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.Visible); From db37de45ac12ebb0ceb9dda45e40d2e75f92c81c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 17:49:06 +0900 Subject: [PATCH 04/14] Allow saving changes to presets in popover using "select" binding --- .../UserInterface/TestSceneModPresetColumn.cs | 9 ++---- .../Graphics/UserInterfaceV2/OsuPopover.cs | 2 +- osu.Game/Overlays/Mods/AddPresetPopover.cs | 14 ++++++++++ osu.Game/Overlays/Mods/EditPresetPopover.cs | 28 ++++++++++++++----- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs index bf6d8e524f..1779b240cc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs @@ -9,8 +9,8 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; using osu.Framework.Graphics.Cursor; +using osu.Framework.Testing; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; @@ -304,11 +304,8 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("preset is not changed", () => panel.Preset.Value.Name == presetName); AddUntilStep("popover is unchanged", () => this.ChildrenOfType().FirstOrDefault() == popover); AddStep("edit preset name", () => popover.ChildrenOfType().First().Current.Value = "something new"); - AddStep("attempt preset edit", () => - { - InputManager.MoveMouseTo(popover.ChildrenOfType().ElementAt(1)); - InputManager.Click(MouseButton.Left); - }); + AddStep("commit changes to textbox", () => InputManager.Key(Key.Enter)); + AddStep("attempt preset edit via select binding", () => InputManager.Key(Key.Enter)); AddUntilStep("popover closed", () => !this.ChildrenOfType().Any()); AddAssert("preset is changed", () => panel.Preset.Value.Name != presetName); } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 00e5b8838c..381193d539 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -65,7 +65,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 return base.OnKeyDown(e); } - public bool OnPressed(KeyBindingPressEvent e) + public virtual bool OnPressed(KeyBindingPressEvent e) { if (e.Repeat) return false; diff --git a/osu.Game/Overlays/Mods/AddPresetPopover.cs b/osu.Game/Overlays/Mods/AddPresetPopover.cs index ef855f6166..638592a9b5 100644 --- a/osu.Game/Overlays/Mods/AddPresetPopover.cs +++ b/osu.Game/Overlays/Mods/AddPresetPopover.cs @@ -8,10 +8,12 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -95,6 +97,18 @@ namespace osu.Game.Overlays.Mods }, true); } + public override bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case GlobalAction.Select: + createButton.TriggerClick(); + return true; + } + + return base.OnPressed(e); + } + private void createPreset() { realm.Write(r => r.Add(new ModPreset diff --git a/osu.Game/Overlays/Mods/EditPresetPopover.cs b/osu.Game/Overlays/Mods/EditPresetPopover.cs index 5220f6a391..d755825a95 100644 --- a/osu.Game/Overlays/Mods/EditPresetPopover.cs +++ b/osu.Game/Overlays/Mods/EditPresetPopover.cs @@ -8,11 +8,13 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; using osuTK; @@ -130,6 +132,25 @@ namespace osu.Game.Overlays.Mods }, true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox)); + } + + public override bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case GlobalAction.Select: + saveButton.TriggerClick(); + return true; + } + + return base.OnPressed(e); + } + private void useCurrentMods() { saveableMods = selectedMods.Value.ToHashSet(); @@ -150,13 +171,6 @@ namespace osu.Game.Overlays.Mods return !saveableMods.SetEquals(selectedMods.Value); } - protected override void LoadComplete() - { - base.LoadComplete(); - - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox)); - } - private void save() { preset.PerformWrite(s => From 6529c810fadf3c2c25dec35441ca7f72d2448bb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 18:17:46 +0900 Subject: [PATCH 05/14] Change order and test population in `TestSceneStatisticsPanel` to give better visible results --- .../Visual/Ranking/TestSceneStatisticsPanel.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 67211a3b72..69c9fcb637 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -30,19 +30,19 @@ namespace osu.Game.Tests.Visual.Ranking public partial class TestSceneStatisticsPanel : OsuTestScene { [Test] - public void TestScoreWithTimeStatistics() + public void TestScoreWithPositionStatistics() { var score = TestResources.CreateTestScoreInfo(); - score.HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(); + score.HitEvents = createPositionDistributedHitEvents(); loadPanel(score); } [Test] - public void TestScoreWithPositionStatistics() + public void TestScoreWithTimeStatistics() { var score = TestResources.CreateTestScoreInfo(); - score.HitEvents = createPositionDistributedHitEvents(); + score.HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(); loadPanel(score); } @@ -89,18 +89,19 @@ namespace osu.Game.Tests.Visual.Ranking private static List createPositionDistributedHitEvents() { - var hitEvents = new List(); + var hitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(); + // Use constant seed for reproducibility var random = new Random(0); - for (int i = 0; i < 500; i++) + for (int i = 0; i < hitEvents.Count; i++) { double angle = random.NextDouble() * 2 * Math.PI; double radius = random.NextDouble() * 0.5f * OsuHitObject.OBJECT_RADIUS; var position = new Vector2((float)(radius * Math.Cos(angle)), (float)(radius * Math.Sin(angle))); - hitEvents.Add(new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), position)); + hitEvents[i] = hitEvents[i].With(position); } return hitEvents; From b7ab46d87b538725086ab4d556f4f19d4118ddd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 01:06:26 +0900 Subject: [PATCH 06/14] Add full statistics score to `TestSceneResultsScreen` --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 5 ++++- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index c5b61c1a90..4eba60de3c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -82,7 +82,10 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both }; - stack.Push(screen = createResultsScreen()); + var score = TestResources.CreateTestScoreInfo(); + score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); + + stack.Push(screen = createResultsScreen(score)); }); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddAssert("retry overlay not present", () => screen.RetryOverlay == null); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 69c9fcb637..e02782678f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking public void TestScoreWithPositionStatistics() { var score = TestResources.CreateTestScoreInfo(); - score.HitEvents = createPositionDistributedHitEvents(); + score.HitEvents = CreatePositionDistributedHitEvents(); loadPanel(score); } @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Ranking }; }); - private static List createPositionDistributedHitEvents() + public static List CreatePositionDistributedHitEvents() { var hitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(); From aebbffacf247adf4d9e64e3205c5c5c421032685 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 19:06:48 +0900 Subject: [PATCH 07/14] Show online stats on `TestSceneStatisticsPanel` Fix test regression --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 1 + osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 4eba60de3c..937bbe1448 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -83,6 +83,7 @@ namespace osu.Game.Tests.Visual.Ranking }; var score = TestResources.CreateTestScoreInfo(); + score.OnlineID = 1234; score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); stack.Push(screen = createResultsScreen(score)); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index e02782678f..e0d743b046 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -23,6 +24,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Tests.Resources; +using osu.Game.Users; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -33,6 +35,7 @@ namespace osu.Game.Tests.Visual.Ranking public void TestScoreWithPositionStatistics() { var score = TestResources.CreateTestScoreInfo(); + score.OnlineID = 1234; score.HitEvents = CreatePositionDistributedHitEvents(); loadPanel(score); @@ -79,11 +82,12 @@ namespace osu.Game.Tests.Visual.Ranking private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { - Child = new StatisticsPanel + Child = new SoloStatisticsPanel(score) { RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible }, - Score = { Value = score } + Score = { Value = score }, + StatisticsUpdate = { Value = new SoloStatisticsUpdate(score, new UserStatistics(), new UserStatistics()) } }; }); From 654a7057fca18ba68bb8590b34b4a06076d014ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 01:29:58 +0900 Subject: [PATCH 08/14] Add actual statistics changes to better visualise layout --- .../Ranking/TestSceneStatisticsPanel.cs | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index e0d743b046..93005271a9 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -87,7 +87,44 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible }, Score = { Value = score }, - StatisticsUpdate = { Value = new SoloStatisticsUpdate(score, new UserStatistics(), new UserStatistics()) } + StatisticsUpdate = + { + Value = new SoloStatisticsUpdate(score, new UserStatistics + { + Level = new UserStatistics.LevelInfo + { + Current = 5, + Progress = 20, + }, + GlobalRank = 38000, + CountryRank = 12006, + PP = 2134, + RankedScore = 21123849, + Accuracy = 0.985, + PlayCount = 13375, + PlayTime = 354490, + TotalScore = 128749597, + TotalHits = 0, + MaxCombo = 1233, + }, new UserStatistics + { + Level = new UserStatistics.LevelInfo + { + Current = 5, + Progress = 30, + }, + GlobalRank = 36000, + CountryRank = 12000, + PP = (decimal)2134.5, + RankedScore = 23897015, + Accuracy = 0.984, + PlayCount = 13376, + PlayTime = 35789, + TotalScore = 132218497, + TotalHits = 0, + MaxCombo = 1233, + }) + } }; }); From 6ef39b87fed8b44b5f9219da12d639ae5e4f422b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 01:24:09 +0900 Subject: [PATCH 09/14] Reorder tests for testability --- .../Visual/Ranking/TestSceneResultsScreen.cs | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 937bbe1448..1c904a936f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -69,6 +69,30 @@ namespace osu.Game.Tests.Visual.Ranking })); } + [TestCase(1, ScoreRank.X)] + [TestCase(0.9999, ScoreRank.S)] + [TestCase(0.975, ScoreRank.S)] + [TestCase(0.925, ScoreRank.A)] + [TestCase(0.85, ScoreRank.B)] + [TestCase(0.75, ScoreRank.C)] + [TestCase(0.5, ScoreRank.D)] + [TestCase(0.2, ScoreRank.D)] + public void TestResultsWithPlayer(double accuracy, ScoreRank rank) + { + TestResultsScreen screen = null; + + var score = TestResources.CreateTestScoreInfo(); + + score.OnlineID = 1234; + score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); + score.Accuracy = accuracy; + score.Rank = rank; + + loadResultsScreen(() => screen = createResultsScreen(score)); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay present", () => screen.RetryOverlay != null); + } + [Test] public void TestResultsWithoutPlayer() { @@ -83,8 +107,6 @@ namespace osu.Game.Tests.Visual.Ranking }; var score = TestResources.CreateTestScoreInfo(); - score.OnlineID = 1234; - score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); stack.Push(screen = createResultsScreen(score)); }); @@ -92,28 +114,6 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("retry overlay not present", () => screen.RetryOverlay == null); } - [TestCase(0.2, ScoreRank.D)] - [TestCase(0.5, ScoreRank.D)] - [TestCase(0.75, ScoreRank.C)] - [TestCase(0.85, ScoreRank.B)] - [TestCase(0.925, ScoreRank.A)] - [TestCase(0.975, ScoreRank.S)] - [TestCase(0.9999, ScoreRank.S)] - [TestCase(1, ScoreRank.X)] - public void TestResultsWithPlayer(double accuracy, ScoreRank rank) - { - TestResultsScreen screen = null; - - var score = TestResources.CreateTestScoreInfo(); - - score.Accuracy = accuracy; - score.Rank = rank; - - loadResultsScreen(() => screen = createResultsScreen(score)); - AddUntilStep("wait for loaded", () => screen.IsLoaded); - AddAssert("retry overlay present", () => screen.RetryOverlay != null); - } - [Test] public void TestResultsForUnranked() { @@ -332,13 +332,14 @@ namespace osu.Game.Tests.Visual.Ranking } } - private partial class TestResultsScreen : ResultsScreen + private partial class TestResultsScreen : SoloResultsScreen { public HotkeyRetryOverlay RetryOverlay; public TestResultsScreen(ScoreInfo score) : base(score, true) { + ShowUserStatistics = true; } protected override void LoadComplete() From 6edaf4f230c4b0ab82f6ca8dbab0e3774cdec1a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Jun 2023 17:52:08 +0900 Subject: [PATCH 10/14] Ensure `PerformanceBreakdown` pieces cannot be null --- .../Rulesets/Difficulty/PerformanceBreakdown.cs | 13 +++++++++++-- .../Difficulty/PerformanceBreakdownCalculator.cs | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs index bd971db476..6e41855ca3 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Difficulty { /// @@ -19,5 +17,16 @@ namespace osu.Game.Rulesets.Difficulty /// Performance of a perfect play for comparison. /// public PerformanceAttributes PerfectPerformance { get; set; } + + /// + /// Create a new performance breakdown. + /// + /// Actual gameplay performance. + /// Performance of a perfect play for comparison. + public PerformanceBreakdown(PerformanceAttributes performance, PerformanceAttributes perfectPerformance) + { + Performance = performance; + PerfectPerformance = perfectPerformance; + } } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs index 8b59500f43..ad9257d4f3 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Difficulty getPerfectPerformance(score, cancellationToken) ).ConfigureAwait(false); - return new PerformanceBreakdown { Performance = performanceArray[0], PerfectPerformance = performanceArray[1] }; + return new PerformanceBreakdown(performanceArray[0] ?? new PerformanceAttributes(), performanceArray[1] ?? new PerformanceAttributes()); } [ItemCanBeNull] From 2073810e958b7376d4797de1dc1e5d79213be7d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 18:48:24 +0900 Subject: [PATCH 11/14] Add performance breakdown chart for osu!catch --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index e51e5cc5db..bd6b857fe8 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -26,6 +26,8 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch @@ -218,5 +220,17 @@ namespace osu.Game.Rulesets.Catch public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier(); + + public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) + { + return new[] + { + new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }), + }; + } } } From 9ad63bae374262f1a1158dcc1975eecda1595e39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 18:50:52 +0900 Subject: [PATCH 12/14] Add missing heading for statistics section in results screen --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index bd6ab4086b..e46c85ab29 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -412,7 +412,7 @@ namespace osu.Game.Rulesets.Mania RelativeSizeAxes = Axes.X, Height = 250 }, true), - new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + new StatisticItem("Statistics", () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { new AverageHitError(score.HitEvents), new UnstableRate(score.HitEvents) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 036d13c5aa..c00bab8ed7 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -319,7 +319,7 @@ namespace osu.Game.Rulesets.Osu RelativeSizeAxes = Axes.X, Height = 250 }, true), - new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + new StatisticItem("Statistics", () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { new AverageHitError(timedHitEvents), new UnstableRate(timedHitEvents) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index de3fa1750f..feb8b0c2c2 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -256,7 +256,7 @@ namespace osu.Game.Rulesets.Taiko RelativeSizeAxes = Axes.X, Height = 250 }, true), - new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[] + new StatisticItem("Statistics", () => new SimpleStatisticTable(3, new SimpleStatisticItem[] { new AverageHitError(timedHitEvents), new UnstableRate(timedHitEvents) From cde8d8e7f1fccc221527209216c24a73612e7d71 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 13 Jul 2023 14:33:21 +0200 Subject: [PATCH 13/14] Optimize realm transactions for editor Save action --- osu.Game/Beatmaps/BeatmapManager.cs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 54c243e842..265149e378 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -431,8 +431,9 @@ namespace osu.Game.Beatmaps beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified; beatmapInfo.ResetOnlineInfo(); - using (var stream = new MemoryStream()) + Realm.Write(r => { + using var stream = new MemoryStream(); using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); @@ -458,23 +459,20 @@ namespace osu.Game.Beatmaps updateHashAndMarkDirty(setInfo); - Realm.Write(r => - { - var liveBeatmapSet = r.Find(setInfo.ID)!; + var liveBeatmapSet = r.Find(setInfo.ID)!; - setInfo.CopyChangesToRealm(liveBeatmapSet); + setInfo.CopyChangesToRealm(liveBeatmapSet); - if (transferCollections) - beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); + if (transferCollections) + beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); - liveBeatmapSet.Beatmaps.Single(b => b.ID == beatmapInfo.ID) - .UpdateLocalScores(r); + liveBeatmapSet.Beatmaps.Single(b => b.ID == beatmapInfo.ID) + .UpdateLocalScores(r); - // do not look up metadata. - // this is a locally-modified set now, so looking up metadata is busy work at best and harmful at worst. - ProcessBeatmap?.Invoke(liveBeatmapSet, MetadataLookupScope.None); - }); - } + // do not look up metadata. + // this is a locally-modified set now, so looking up metadata is busy work at best and harmful at worst. + ProcessBeatmap?.Invoke(liveBeatmapSet, MetadataLookupScope.None); + }); Debug.Assert(beatmapInfo.BeatmapSet != null); From 4b06b6f34ec98b37bdd68e597a8248ac9c36df89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 Jul 2023 18:53:32 +0200 Subject: [PATCH 14/14] Crudely fix crashes when switching between `TestResultsWithPlayer` cases In the visual test browser, if two `TestResultsWithPlayer` test cases are ran consecutively, the second would die on `SoloStatisticsWatcher` seeing duplicated online score IDs. This surfaced after 6ef39b87fed8b44b5f9219da12d639ae5e4f422b, which changed `TestResultsScreen` to inherit `SoloResultsScreen` rather than `ResultsScreen`. This is probably _not_ a very good fix, but I'm trying to be pragmatic for now. `SoloStatisticsWatcher` should probably not live in `OsuGameBase`. --- .../Visual/Ranking/TestSceneResultsScreen.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 1c904a936f..146482e6fb 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -69,6 +69,8 @@ namespace osu.Game.Tests.Visual.Ranking })); } + private int onlineScoreID = 1; + [TestCase(1, ScoreRank.X)] [TestCase(0.9999, ScoreRank.S)] [TestCase(0.975, ScoreRank.S)] @@ -81,14 +83,17 @@ namespace osu.Game.Tests.Visual.Ranking { TestResultsScreen screen = null; - var score = TestResources.CreateTestScoreInfo(); + loadResultsScreen(() => + { + var score = TestResources.CreateTestScoreInfo(); - score.OnlineID = 1234; - score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); - score.Accuracy = accuracy; - score.Rank = rank; + score.OnlineID = onlineScoreID++; + score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); + score.Accuracy = accuracy; + score.Rank = rank; - loadResultsScreen(() => screen = createResultsScreen(score)); + return screen = createResultsScreen(score); + }); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddAssert("retry overlay present", () => screen.RetryOverlay != null); }