From 9aa3021bbc5f1f2ba2383cb96aef012d19ba5713 Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Wed, 3 Nov 2021 22:22:44 -0600 Subject: [PATCH 001/169] 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 002/169] 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 003/169] 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 004/169] 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 005/169] 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 006/169] 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 007/169] 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 54f72d68cab6ba44f9f8b5fe4d82b8b0cede0225 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Nov 2021 18:05:31 +0900 Subject: [PATCH 008/169] Revert weird event flow in model manager/importers --- .../Beatmaps/IO/ImportBeatmapTest.cs | 4 +- .../Online/TestSceneDirectDownloadButton.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 37 +++++++-- osu.Game/Beatmaps/BeatmapModelManager.cs | 13 +-- osu.Game/Database/ArchiveModelManager.cs | 13 +-- osu.Game/Database/IModelDownloader.cs | 5 +- osu.Game/Database/IModelManager.cs | 11 +-- osu.Game/Database/ModelDownloader.cs | 15 ++-- osu.Game/Online/BeatmapDownloadTracker.cs | 82 +++++++------------ osu.Game/Online/ScoreDownloadTracker.cs | 82 +++++++------------ osu.Game/OsuGameBase.cs | 27 ++---- osu.Game/Overlays/MusicController.cs | 45 ++++------ .../Overlays/Settings/Sections/SkinSection.cs | 33 ++++---- osu.Game/Scoring/ScoreManager.cs | 24 +++++- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 15 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 57 +++++-------- .../Screens/Select/Carousel/TopLocalRank.cs | 31 +++---- .../Select/Leaderboards/BeatmapLeaderboard.cs | 37 ++++----- osu.Game/Screens/Spectate/SpectatorScreen.cs | 16 ++-- 19 files changed, 236 insertions(+), 315 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index d68d43c998..f815598122 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -408,8 +408,8 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); // ReSharper disable once AccessToModifiedClosure - manager.ItemUpdated.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount)); - manager.ItemRemoved.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount)); + manager.ItemUpdated += _ => Interlocked.Increment(ref itemAddRemoveFireCount); + manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount); var imported = await LoadOszIntoOsu(osu); diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index 3d828077c8..a865bbe950 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -45,10 +45,10 @@ namespace osu.Game.Tests.Visual.Online AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); - AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); + AddUntilStep("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); createButtonWithBeatmap(createSoleily()); - AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); + AddUntilStep("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); ensureSoleilyRemoved(); AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7a9f3e0a45..98c0ba648e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Testing; @@ -101,12 +100,20 @@ namespace osu.Game.Beatmaps /// /// Fired when a single difficulty has been hidden. /// - public IBindable> BeatmapHidden => beatmapModelManager.BeatmapHidden; + public Action BeatmapHidden + { + get => beatmapModelManager.BeatmapHidden; + set => beatmapModelManager.BeatmapHidden = value; + } /// /// Fired when a single difficulty has been restored. /// - public IBindable> BeatmapRestored => beatmapModelManager.BeatmapRestored; + public Action BeatmapRestored + { + get => beatmapModelManager.BeatmapRestored; + set => beatmapModelManager.BeatmapRestored = value; + } /// /// Saves an file against a given . @@ -198,9 +205,17 @@ namespace osu.Game.Beatmaps return beatmapModelManager.IsAvailableLocally(model); } - public IBindable> ItemUpdated => beatmapModelManager.ItemUpdated; + public Action ItemUpdated + { + get => beatmapModelManager.ItemUpdated; + set => beatmapModelManager.ItemUpdated = value; + } - public IBindable> ItemRemoved => beatmapModelManager.ItemRemoved; + public Action ItemRemoved + { + get => beatmapModelManager.ItemRemoved; + set => beatmapModelManager.ItemRemoved = value; + } public Task ImportFromStableAsync(StableStorage stableStorage) { @@ -246,9 +261,17 @@ namespace osu.Game.Beatmaps #region Implementation of IModelDownloader - public IBindable>> DownloadBegan => beatmapModelDownloader.DownloadBegan; + public Action> DownloadBegan + { + get => beatmapModelDownloader.DownloadBegan; + set => beatmapModelDownloader.DownloadBegan = value; + } - public IBindable>> DownloadFailed => beatmapModelDownloader.DownloadFailed; + public Action> DownloadFailed + { + get => beatmapModelDownloader.DownloadFailed; + set => beatmapModelDownloader.DownloadFailed = value; + } // Temporary method until this class supports IBeatmapSetInfo or otherwise. public bool Download(IBeatmapSetInfo model, bool minimiseDownloadSize = false) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index f148d05aca..1a78a625d9 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using osu.Framework.Audio.Track; -using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; @@ -37,14 +36,12 @@ namespace osu.Game.Beatmaps /// /// Fired when a single difficulty has been hidden. /// - public IBindable> BeatmapHidden => beatmapHidden; - - private readonly Bindable> beatmapHidden = new Bindable>(); + public Action BeatmapHidden; /// /// Fired when a single difficulty has been restored. /// - public IBindable> BeatmapRestored => beatmapRestored; + public Action BeatmapRestored; /// /// An online lookup queue component which handles populating online beatmap metadata. @@ -56,8 +53,6 @@ namespace osu.Game.Beatmaps /// public IWorkingBeatmapCache WorkingBeatmapCache { private get; set; } - private readonly Bindable> beatmapRestored = new Bindable>(); - public override IEnumerable HandledExtensions => new[] { ".osz" }; protected override string[] HashableFileTypes => new[] { ".osu" }; @@ -75,8 +70,8 @@ namespace osu.Game.Beatmaps this.rulesets = rulesets; beatmaps = (BeatmapStore)ModelStore; - beatmaps.BeatmapHidden += b => beatmapHidden.Value = new WeakReference(b); - beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference(b); + beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); + beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); beatmaps.ItemRemoved += b => WorkingBeatmapCache?.Invalidate(b); beatmaps.ItemUpdated += obj => WorkingBeatmapCache?.Invalidate(obj); } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 4e40e52051..bc4d0cdabd 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Humanizer; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; @@ -63,17 +62,13 @@ namespace osu.Game.Database /// Fired when a new or updated becomes available in the database. /// This is not guaranteed to run on the update thread. /// - public IBindable> ItemUpdated => itemUpdated; - - private readonly Bindable> itemUpdated = new Bindable>(); + public Action ItemUpdated { get; set; } /// /// Fired when a is removed from the database. /// This is not guaranteed to run on the update thread. /// - public IBindable> ItemRemoved => itemRemoved; - - private readonly Bindable> itemRemoved = new Bindable>(); + public Action ItemRemoved { get; set; } public virtual IEnumerable HandledExtensions => new[] { @".zip" }; @@ -93,8 +88,8 @@ namespace osu.Game.Database ContextFactory = contextFactory; ModelStore = modelStore; - ModelStore.ItemUpdated += item => handleEvent(() => itemUpdated.Value = new WeakReference(item)); - ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item)); + ModelStore.ItemUpdated += item => handleEvent(() => ItemUpdated?.Invoke(item)); + ModelStore.ItemRemoved += item => handleEvent(() => ItemRemoved?.Invoke(item)); exportStorage = storage.GetStorageForDirectory(@"exports"); diff --git a/osu.Game/Database/IModelDownloader.cs b/osu.Game/Database/IModelDownloader.cs index a5573b2190..09bf838eeb 100644 --- a/osu.Game/Database/IModelDownloader.cs +++ b/osu.Game/Database/IModelDownloader.cs @@ -3,7 +3,6 @@ using System; using osu.Game.Online.API; -using osu.Framework.Bindables; namespace osu.Game.Database { @@ -18,13 +17,13 @@ namespace osu.Game.Database /// Fired when a download begins. /// This is NOT run on the update thread and should be scheduled. /// - IBindable>> DownloadBegan { get; } + Action> DownloadBegan { get; set; } /// /// Fired when a download is interrupted, either due to user cancellation or failure. /// This is NOT run on the update thread and should be scheduled. /// - IBindable>> DownloadFailed { get; } + Action> DownloadFailed { get; set; } /// /// Begin a download for the requested . diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index f5e401cdfb..d8e5775f4d 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using osu.Framework.Bindables; using osu.Game.IO; namespace osu.Game.Database @@ -18,16 +17,14 @@ namespace osu.Game.Database where TModel : class { /// - /// A bindable which contains a weak reference to the last item that was updated. - /// This is not thread-safe and should be scheduled locally if consumed from a drawable component. + /// Fired when an item is updated. /// - IBindable> ItemUpdated { get; } + Action ItemUpdated { get; set; } /// - /// A bindable which contains a weak reference to the last item that was removed. - /// This is not thread-safe and should be scheduled locally if consumed from a drawable component. + /// Fired when an item is removed. /// - IBindable> ItemRemoved { get; } + Action ItemRemoved { get; set; } /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 12bf5e9ce7..39ca4780eb 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Humanizer; -using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Online.API; @@ -19,13 +18,9 @@ namespace osu.Game.Database { public Action PostNotification { protected get; set; } - public IBindable>> DownloadBegan => downloadBegan; + public Action> DownloadBegan { get; set; } - private readonly Bindable>> downloadBegan = new Bindable>>(); - - public IBindable>> DownloadFailed => downloadFailed; - - private readonly Bindable>> downloadFailed = new Bindable>>(); + public Action> DownloadFailed { get; set; } private readonly IModelManager modelManager; private readonly IAPIProvider api; @@ -72,7 +67,7 @@ namespace osu.Game.Database // for now a failed import will be marked as a failed download for simplicity. if (!imported.Any()) - downloadFailed.Value = new WeakReference>(request); + DownloadFailed?.Invoke(request); CurrentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); @@ -91,14 +86,14 @@ namespace osu.Game.Database api.PerformAsync(request); - downloadBegan.Value = new WeakReference>(request); + DownloadBegan?.Invoke(request); return true; void triggerFailure(Exception error) { CurrentDownloads.Remove(request); - downloadFailed.Value = new WeakReference>(request); + DownloadFailed?.Invoke(request); notification.State = ProgressNotificationState.Cancelled; diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 4a7d0b660a..6637c0705e 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online.API; @@ -23,11 +22,6 @@ namespace osu.Game.Online { } - private IBindable>? managerUpdated; - private IBindable>? managerRemoved; - private IBindable>>? managerDownloadBegan; - private IBindable>>? managerDownloadFailed; - [BackgroundDependencyLoader(true)] private void load() { @@ -42,39 +36,23 @@ namespace osu.Game.Online else attachDownload(Manager.GetExistingDownload(beatmapSetInfo)); - managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy(); - managerDownloadBegan.BindValueChanged(downloadBegan); - managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy(); - managerDownloadFailed.BindValueChanged(downloadFailed); - managerUpdated = Manager.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(itemUpdated); - managerRemoved = Manager.ItemRemoved.GetBoundCopy(); - managerRemoved.BindValueChanged(itemRemoved); + Manager.DownloadBegan += downloadBegan; + Manager.DownloadFailed += downloadFailed; + Manager.ItemUpdated += itemUpdated; + Manager.ItemRemoved += itemRemoved; } - private void downloadBegan(ValueChangedEvent>> weakRequest) + private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => { - if (weakRequest.NewValue.TryGetTarget(out var request)) - { - Schedule(() => - { - if (checkEquality(request.Model, TrackedItem)) - attachDownload(request); - }); - } - } + if (checkEquality(request.Model, TrackedItem)) + attachDownload(request); + }); - private void downloadFailed(ValueChangedEvent>> weakRequest) + private void downloadFailed(ArchiveDownloadRequest request) => Schedule(() => { - if (weakRequest.NewValue.TryGetTarget(out var request)) - { - Schedule(() => - { - if (checkEquality(request.Model, TrackedItem)) - attachDownload(null); - }); - } - } + if (checkEquality(request.Model, TrackedItem)) + attachDownload(null); + }); private void attachDownload(ArchiveDownloadRequest? request) { @@ -116,29 +94,17 @@ namespace osu.Game.Online private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); - private void itemUpdated(ValueChangedEvent> weakItem) + private void itemUpdated(BeatmapSetInfo item) => Schedule(() => { - if (weakItem.NewValue.TryGetTarget(out var item)) - { - Schedule(() => - { - if (checkEquality(item, TrackedItem)) - UpdateState(DownloadState.LocallyAvailable); - }); - } - } + if (checkEquality(item, TrackedItem)) + UpdateState(DownloadState.LocallyAvailable); + }); - private void itemRemoved(ValueChangedEvent> weakItem) + private void itemRemoved(BeatmapSetInfo item) => Schedule(() => { - if (weakItem.NewValue.TryGetTarget(out var item)) - { - Schedule(() => - { - if (checkEquality(item, TrackedItem)) - UpdateState(DownloadState.NotDownloaded); - }); - } - } + if (checkEquality(item, TrackedItem)) + UpdateState(DownloadState.NotDownloaded); + }); private bool checkEquality(IBeatmapSetInfo x, IBeatmapSetInfo y) => x.OnlineID == y.OnlineID; @@ -148,6 +114,14 @@ namespace osu.Game.Online { base.Dispose(isDisposing); attachDownload(null); + + if (Manager != null) + { + Manager.DownloadBegan -= downloadBegan; + Manager.DownloadFailed -= downloadFailed; + Manager.ItemUpdated -= itemUpdated; + Manager.ItemRemoved -= itemRemoved; + } } #endregion diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index e679071ac1..37031ed930 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -3,7 +3,6 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Scoring; @@ -23,11 +22,6 @@ namespace osu.Game.Online { } - private IBindable>? managerUpdated; - private IBindable>? managerRemoved; - private IBindable>>? managerDownloadBegan; - private IBindable>>? managerDownloadFailed; - [BackgroundDependencyLoader(true)] private void load() { @@ -46,39 +40,23 @@ namespace osu.Game.Online else attachDownload(Manager.GetExistingDownload(scoreInfo)); - managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy(); - managerDownloadBegan.BindValueChanged(downloadBegan); - managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy(); - managerDownloadFailed.BindValueChanged(downloadFailed); - managerUpdated = Manager.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(itemUpdated); - managerRemoved = Manager.ItemRemoved.GetBoundCopy(); - managerRemoved.BindValueChanged(itemRemoved); + Manager.DownloadBegan += downloadBegan; + Manager.DownloadFailed += downloadFailed; + Manager.ItemUpdated += itemUpdated; + Manager.ItemRemoved += itemRemoved; } - private void downloadBegan(ValueChangedEvent>> weakRequest) + private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => { - if (weakRequest.NewValue.TryGetTarget(out var request)) - { - Schedule(() => - { - if (checkEquality(request.Model, TrackedItem)) - attachDownload(request); - }); - } - } + if (checkEquality(request.Model, TrackedItem)) + attachDownload(request); + }); - private void downloadFailed(ValueChangedEvent>> weakRequest) + private void downloadFailed(ArchiveDownloadRequest request) => Schedule(() => { - if (weakRequest.NewValue.TryGetTarget(out var request)) - { - Schedule(() => - { - if (checkEquality(request.Model, TrackedItem)) - attachDownload(null); - }); - } - } + if (checkEquality(request.Model, TrackedItem)) + attachDownload(null); + }); private void attachDownload(ArchiveDownloadRequest? request) { @@ -120,29 +98,17 @@ namespace osu.Game.Online private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); - private void itemUpdated(ValueChangedEvent> weakItem) + private void itemUpdated(ScoreInfo item) => Schedule(() => { - if (weakItem.NewValue.TryGetTarget(out var item)) - { - Schedule(() => - { - if (checkEquality(item, TrackedItem)) - UpdateState(DownloadState.LocallyAvailable); - }); - } - } + if (checkEquality(item, TrackedItem)) + UpdateState(DownloadState.LocallyAvailable); + }); - private void itemRemoved(ValueChangedEvent> weakItem) + private void itemRemoved(ScoreInfo item) => Schedule(() => { - if (weakItem.NewValue.TryGetTarget(out var item)) - { - Schedule(() => - { - if (checkEquality(item, TrackedItem)) - UpdateState(DownloadState.NotDownloaded); - }); - } - } + if (checkEquality(item, TrackedItem)) + UpdateState(DownloadState.LocallyAvailable); + }); private bool checkEquality(ScoreInfo x, ScoreInfo y) => x.OnlineScoreID == y.OnlineScoreID; @@ -152,6 +118,14 @@ namespace osu.Game.Online { base.Dispose(isDisposing); attachDownload(null); + + if (Manager != null) + { + Manager.DownloadBegan -= downloadBegan; + Manager.DownloadFailed -= downloadFailed; + Manager.ItemUpdated -= itemUpdated; + Manager.ItemRemoved -= itemRemoved; + } } #endregion diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f6ec22a536..bf4f968e4e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -206,17 +206,11 @@ namespace osu.Game dependencies.CacheAs(SkinManager); // needs to be done here rather than inside SkinManager to ensure thread safety of CurrentSkinInfo. - SkinManager.ItemRemoved.BindValueChanged(weakRemovedInfo => + SkinManager.ItemRemoved += item => Schedule(() => { - if (weakRemovedInfo.NewValue.TryGetTarget(out var removedInfo)) - { - Schedule(() => - { - // check the removed skin is not the current user choice. if it is, switch back to default. - if (removedInfo.ID == SkinManager.CurrentSkinInfo.Value.ID) - SkinManager.CurrentSkinInfo.Value = SkinInfo.Default; - }); - } + // check the removed skin is not the current user choice. if it is, switch back to default. + if (item.ID == SkinManager.CurrentSkinInfo.Value.ID) + SkinManager.CurrentSkinInfo.Value = SkinInfo.Default; }); EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); @@ -246,17 +240,8 @@ namespace osu.Game return ScoreManager.QueryScores(s => beatmapIds.Contains(s.BeatmapInfo.ID)).ToList(); } - BeatmapManager.ItemRemoved.BindValueChanged(i => - { - if (i.NewValue.TryGetTarget(out var item)) - ScoreManager.Delete(getBeatmapScores(item), true); - }); - - BeatmapManager.ItemUpdated.BindValueChanged(i => - { - if (i.NewValue.TryGetTarget(out var item)) - ScoreManager.Undelete(getBeatmapScores(item), true); - }); + BeatmapManager.ItemRemoved += item => ScoreManager.Delete(getBeatmapScores(item), true); + BeatmapManager.ItemUpdated += item => ScoreManager.Undelete(getBeatmapScores(item), true); dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); AddInternal(difficultyCache); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 97c7aaeaeb..829ff5be25 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -65,16 +65,11 @@ namespace osu.Game.Overlays [NotNull] public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); - private IBindable> managerUpdated; - private IBindable> managerRemoved; - [BackgroundDependencyLoader] private void load() { - managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(beatmapUpdated); - managerRemoved = beatmaps.ItemRemoved.GetBoundCopy(); - managerRemoved.BindValueChanged(beatmapRemoved); + beatmaps.ItemUpdated += beatmapUpdated; + beatmaps.ItemRemoved += beatmapRemoved; beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal, true).OrderBy(_ => RNG.Next())); @@ -110,28 +105,13 @@ namespace osu.Game.Overlays /// public bool TrackLoaded => CurrentTrack.TrackLoaded; - private void beatmapUpdated(ValueChangedEvent> weakSet) + private void beatmapUpdated(BeatmapSetInfo set) => Schedule(() => { - if (weakSet.NewValue.TryGetTarget(out var set)) - { - Schedule(() => - { - beatmapSets.Remove(set); - beatmapSets.Add(set); - }); - } - } + beatmapSets.Remove(set); + beatmapSets.Add(set); + }); - private void beatmapRemoved(ValueChangedEvent> weakSet) - { - if (weakSet.NewValue.TryGetTarget(out var set)) - { - Schedule(() => - { - beatmapSets.RemoveAll(s => s.ID == set.ID); - }); - } - } + private void beatmapRemoved(BeatmapSetInfo set) => Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); private ScheduledDelegate seekDelegate; @@ -437,6 +417,17 @@ namespace osu.Game.Overlays mod.ApplyToTrack(CurrentTrack); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmaps != null) + { + beatmaps.ItemUpdated -= beatmapUpdated; + beatmaps.ItemRemoved -= beatmapRemoved; + } + } } public enum TrackChangeDirection diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index cf5d70ba91..0714b28b47 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -56,9 +56,6 @@ namespace osu.Game.Overlays.Settings.Sections [Resolved] private SkinManager skins { get; set; } - private IBindable> managerUpdated; - private IBindable> managerRemoved; - [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor) { @@ -76,11 +73,8 @@ namespace osu.Game.Overlays.Settings.Sections new ExportSkinButton(), }; - managerUpdated = skins.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(itemUpdated); - - managerRemoved = skins.ItemRemoved.GetBoundCopy(); - managerRemoved.BindValueChanged(itemRemoved); + skins.ItemUpdated += itemUpdated; + skins.ItemRemoved += itemRemoved; config.BindWith(OsuSetting.Skin, configBindable); @@ -129,11 +123,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Items = skinItems; } - private void itemUpdated(ValueChangedEvent> weakItem) - { - if (weakItem.NewValue.TryGetTarget(out var item)) - Schedule(() => addItem(item)); - } + private void itemUpdated(SkinInfo item) => Schedule(() => addItem(item)); private void addItem(SkinInfo item) { @@ -142,11 +132,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Items = newDropdownItems; } - private void itemRemoved(ValueChangedEvent> weakItem) - { - if (weakItem.NewValue.TryGetTarget(out var item)) - Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != item.ID).ToArray()); - } + private void itemRemoved(SkinInfo item) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != item.ID).ToArray()); private void sortUserSkins(List skinsList) { @@ -155,6 +141,17 @@ namespace osu.Game.Overlays.Settings.Sections Comparer.Create((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase))); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (skins != null) + { + skins.ItemUpdated -= itemUpdated; + skins.ItemRemoved -= itemRemoved; + } + } + private class SkinSettingsDropdown : SettingsDropdown { protected override OsuDropdown CreateDropdown() => new SkinDropdownControl(); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 253591eb56..b5348cd179 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -246,9 +246,17 @@ namespace osu.Game.Scoring #region Implementation of IModelManager - public IBindable> ItemUpdated => scoreModelManager.ItemUpdated; + public Action ItemUpdated + { + get => scoreModelManager.ItemUpdated; + set => scoreModelManager.ItemUpdated = value; + } - public IBindable> ItemRemoved => scoreModelManager.ItemRemoved; + public Action ItemRemoved + { + get => scoreModelManager.ItemRemoved; + set => scoreModelManager.ItemRemoved = value; + } public Task ImportFromStableAsync(StableStorage stableStorage) { @@ -350,9 +358,17 @@ namespace osu.Game.Scoring #region Implementation of IModelDownloader - public IBindable>> DownloadBegan => scoreModelDownloader.DownloadBegan; + public Action> DownloadBegan + { + get => scoreModelDownloader.DownloadBegan; + set => scoreModelDownloader.DownloadBegan = value; + } - public IBindable>> DownloadFailed => scoreModelDownloader.DownloadFailed; + public Action> DownloadFailed + { + get => scoreModelDownloader.DownloadFailed; + set => scoreModelDownloader.DownloadFailed = value; + } public bool Download(ScoreInfo model, bool minimiseDownloadSize) { diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 2cb29262e2..d6016ec4b9 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -62,8 +62,6 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved(canBeNull: true)] protected OnlinePlayScreen ParentScreen { get; private set; } - private IBindable> managerUpdated; - [Cached] private OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker { get; set; } @@ -246,8 +244,7 @@ namespace osu.Game.Screens.OnlinePlay.Match SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); - managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(beatmapUpdated); + beatmapManager.ItemUpdated += beatmapUpdated; UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods)); } @@ -362,7 +359,7 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - private void beatmapUpdated(ValueChangedEvent> weakSet) => Schedule(updateWorkingBeatmap); + private void beatmapUpdated(BeatmapSetInfo set) => Schedule(updateWorkingBeatmap); private void updateWorkingBeatmap() { @@ -431,6 +428,14 @@ namespace osu.Game.Screens.OnlinePlay.Match /// The room to change the settings of. protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room); + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmapManager != null) + beatmapManager.ItemUpdated -= beatmapUpdated; + } + public class UserModSelectButton : PurpleTriangleButton { } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e5e28d2fde..0c593ebea1 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -143,11 +143,6 @@ namespace osu.Game.Screens.Select private CarouselRoot root; - private IBindable> itemUpdated; - private IBindable> itemRemoved; - private IBindable> itemHidden; - private IBindable> itemRestored; - private readonly DrawablePool setPool = new DrawablePool(100); public BeatmapCarousel() @@ -179,14 +174,10 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); - itemUpdated = beatmaps.ItemUpdated.GetBoundCopy(); - itemUpdated.BindValueChanged(beatmapUpdated); - itemRemoved = beatmaps.ItemRemoved.GetBoundCopy(); - itemRemoved.BindValueChanged(beatmapRemoved); - itemHidden = beatmaps.BeatmapHidden.GetBoundCopy(); - itemHidden.BindValueChanged(beatmapHidden); - itemRestored = beatmaps.BeatmapRestored.GetBoundCopy(); - itemRestored.BindValueChanged(beatmapRestored); + beatmaps.ItemUpdated += beatmapUpdated; + beatmaps.ItemRemoved += beatmapRemoved; + beatmaps.BeatmapHidden += beatmapHidden; + beatmaps.BeatmapRestored += beatmapRestored; if (!beatmapSets.Any()) loadBeatmapSets(GetLoadableBeatmaps()); @@ -675,29 +666,10 @@ namespace osu.Game.Screens.Select return (firstIndex, lastIndex); } - private void beatmapRemoved(ValueChangedEvent> weakItem) - { - if (weakItem.NewValue.TryGetTarget(out var item)) - RemoveBeatmapSet(item); - } - - private void beatmapUpdated(ValueChangedEvent> weakItem) - { - if (weakItem.NewValue.TryGetTarget(out var item)) - UpdateBeatmapSet(item); - } - - private void beatmapRestored(ValueChangedEvent> weakItem) - { - if (weakItem.NewValue.TryGetTarget(out var b)) - UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); - } - - private void beatmapHidden(ValueChangedEvent> weakItem) - { - if (weakItem.NewValue.TryGetTarget(out var b)) - UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); - } + private void beatmapRemoved(BeatmapSetInfo item) => RemoveBeatmapSet(item); + private void beatmapUpdated(BeatmapSetInfo item) => UpdateBeatmapSet(item); + private void beatmapRestored(BeatmapInfo b) => UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); + private void beatmapHidden(BeatmapInfo b) => UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) { @@ -956,5 +928,18 @@ namespace osu.Game.Screens.Select return base.OnDragStart(e); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmaps != null) + { + beatmaps.ItemUpdated -= beatmapUpdated; + beatmaps.ItemRemoved -= beatmapRemoved; + beatmaps.BeatmapHidden -= beatmapHidden; + beatmaps.BeatmapRestored -= beatmapRestored; + } + } } } diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index f2485587d8..34129f232c 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -1,7 +1,6 @@ // 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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -28,9 +27,6 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private IAPIProvider api { get; set; } - private IBindable> itemUpdated; - private IBindable> itemRemoved; - public TopLocalRank(BeatmapInfo beatmapInfo) : base(null) { @@ -40,24 +36,18 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader] private void load() { - itemUpdated = scores.ItemUpdated.GetBoundCopy(); - itemUpdated.BindValueChanged(scoreChanged); - - itemRemoved = scores.ItemRemoved.GetBoundCopy(); - itemRemoved.BindValueChanged(scoreChanged); + scores.ItemUpdated += scoreChanged; + scores.ItemRemoved += scoreChanged; ruleset.ValueChanged += _ => fetchAndLoadTopScore(); fetchAndLoadTopScore(); } - private void scoreChanged(ValueChangedEvent> weakScore) + private void scoreChanged(ScoreInfo score) { - if (weakScore.NewValue.TryGetTarget(out var score)) - { - if (score.BeatmapInfoID == beatmapInfo.ID) - fetchAndLoadTopScore(); - } + if (score.BeatmapInfoID == beatmapInfo.ID) + fetchAndLoadTopScore(); } private ScheduledDelegate scheduledRankUpdate; @@ -86,5 +76,16 @@ namespace osu.Game.Screens.Select.Carousel .OrderByDescending(s => s.TotalScore) .FirstOrDefault(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (scores != null) + { + scores.ItemUpdated -= scoreChanged; + scores.ItemRemoved -= scoreChanged; + } + } } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index de1ba5cf2e..9205c6c0d2 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -44,10 +44,6 @@ namespace osu.Game.Screens.Select.Leaderboards private bool filterMods; - private IBindable> itemRemoved; - - private IBindable> itemAdded; - /// /// Whether to apply the game's currently selected mods as a filter when retrieving scores. /// @@ -90,11 +86,8 @@ namespace osu.Game.Screens.Select.Leaderboards UpdateScores(); }; - itemRemoved = scoreManager.ItemRemoved.GetBoundCopy(); - itemRemoved.BindValueChanged(onScoreRemoved); - - itemAdded = scoreManager.ItemUpdated.GetBoundCopy(); - itemAdded.BindValueChanged(onScoreAdded); + scoreManager.ItemRemoved += scoreStoreChanged; + scoreManager.ItemUpdated += scoreStoreChanged; } protected override void Reset() @@ -103,22 +96,13 @@ namespace osu.Game.Screens.Select.Leaderboards TopScore = null; } - private void onScoreRemoved(ValueChangedEvent> score) => - scoreStoreChanged(score); - - private void onScoreAdded(ValueChangedEvent> score) => - scoreStoreChanged(score); - - private void scoreStoreChanged(ValueChangedEvent> score) + private void scoreStoreChanged(ScoreInfo score) { if (Scope != BeatmapLeaderboardScope.Local) return; - if (score.NewValue.TryGetTarget(out var scoreInfo)) - { - if (BeatmapInfo?.ID != scoreInfo.BeatmapInfoID) - return; - } + if (BeatmapInfo?.ID != score.BeatmapInfoID) + return; RefreshScores(); } @@ -215,5 +199,16 @@ namespace osu.Game.Screens.Select.Leaderboards { Action = () => ScoreSelected?.Invoke(model) }; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (scoreManager != null) + { + scoreManager.ItemRemoved -= scoreStoreChanged; + scoreManager.ItemUpdated -= scoreStoreChanged; + } + } } } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index c8df01dae6..f797f6e808 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -1,7 +1,6 @@ // 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; using System.Linq; @@ -45,8 +44,6 @@ namespace osu.Game.Screens.Spectate private readonly Dictionary userMap = new Dictionary(); private readonly Dictionary gameplayStates = new Dictionary(); - private IBindable> managerUpdated; - /// /// Creates a new . /// @@ -73,20 +70,16 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(beatmapUpdated); + beatmaps.ItemUpdated += beatmapUpdated; foreach ((int id, var _) in userMap) spectatorClient.WatchUser(id); })); } - private void beatmapUpdated(ValueChangedEvent> e) + private void beatmapUpdated(BeatmapSetInfo beatmapSet) { - if (!e.NewValue.TryGetTarget(out var beatmapSet)) - return; - - foreach ((int userId, var _) in userMap) + foreach ((int userId, _) in userMap) { if (!playingUserStates.TryGetValue(userId, out var userState)) continue; @@ -223,7 +216,8 @@ namespace osu.Game.Screens.Spectate spectatorClient.StopWatchingUser(userId); } - managerUpdated?.UnbindAll(); + if (beatmaps != null) + beatmaps.ItemUpdated -= beatmapUpdated; } } } From 77e853ce2593f9973b89f4386592a12fda972e1f Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Fri, 5 Nov 2021 12:16:58 -0600 Subject: [PATCH 009/169] 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 b8fb22b769d15a26abdc9a11c0ca9c8840e7b8d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Nov 2021 16:45:27 +0900 Subject: [PATCH 010/169] Add missing test coverage of score import process being tracked correctly --- .../Gameplay/TestSceneReplayDownloadButton.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 64d9addc77..e178550298 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -11,6 +11,7 @@ using osu.Game.Users; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Testing; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -113,6 +114,36 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } + [Resolved] + private ScoreManager scoreManager { get; set; } + + [Test] + public void TestScoreImportThenDelete() + { + ILive imported = null; + + AddStep("create button without replay", () => + { + Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(false)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddUntilStep("wait for load", () => downloadButton.IsLoaded); + + AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + + AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).Result); + + AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable); + + AddStep("delete score", () => scoreManager.Delete(imported.Value)); + + AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + } + [Test] public void CreateButtonWithNoScore() { From 96ef210a4dda4b4b4fd6a78b3005a39590bd3262 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Nov 2021 16:38:42 +0900 Subject: [PATCH 011/169] Fix backward state set --- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 554aaba39d..32307fc50e 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -107,7 +107,7 @@ namespace osu.Game.Online private void itemRemoved(ScoreInfo item) => Schedule(() => { if (checkEquality(item, TrackedItem)) - UpdateState(DownloadState.LocallyAvailable); + UpdateState(DownloadState.NotDownloaded); }); private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.OnlineID == y.OnlineID; From 89cc2523efbc3b4e62f395f55401d2144b624df8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Nov 2021 22:31:49 +0900 Subject: [PATCH 012/169] Fix incorrectly specified events --- osu.Game/Beatmaps/BeatmapManager.cs | 24 ++++++++++++------------ osu.Game/Database/ArchiveModelManager.cs | 4 ++-- osu.Game/Database/IModelDownloader.cs | 4 ++-- osu.Game/Database/IModelManager.cs | 4 ++-- osu.Game/Database/ModelDownloader.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 24 ++++++++++++------------ 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index da0c379f90..0f2cf6a730 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -205,16 +205,16 @@ namespace osu.Game.Beatmaps return beatmapModelManager.IsAvailableLocally(model); } - public Action ItemUpdated + public event Action ItemUpdated { - get => beatmapModelManager.ItemUpdated; - set => beatmapModelManager.ItemUpdated = value; + add => beatmapModelManager.ItemUpdated += value; + remove => beatmapModelManager.ItemUpdated -= value; } - public Action ItemRemoved + public event Action ItemRemoved { - get => beatmapModelManager.ItemRemoved; - set => beatmapModelManager.ItemRemoved = value; + add => beatmapModelManager.ItemRemoved += value; + remove => beatmapModelManager.ItemRemoved -= value; } public Task ImportFromStableAsync(StableStorage stableStorage) @@ -261,16 +261,16 @@ namespace osu.Game.Beatmaps #region Implementation of IModelDownloader - public Action> DownloadBegan + public event Action> DownloadBegan { - get => beatmapModelDownloader.DownloadBegan; - set => beatmapModelDownloader.DownloadBegan = value; + add => beatmapModelDownloader.DownloadBegan += value; + remove => beatmapModelDownloader.DownloadBegan -= value; } - public Action> DownloadFailed + public event Action> DownloadFailed { - get => beatmapModelDownloader.DownloadFailed; - set => beatmapModelDownloader.DownloadFailed = value; + add => beatmapModelDownloader.DownloadFailed += value; + remove => beatmapModelDownloader.DownloadFailed -= value; } public bool Download(IBeatmapSetInfo model, bool minimiseDownloadSize = false) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 85419aeedb..019749760f 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -62,13 +62,13 @@ namespace osu.Game.Database /// Fired when a new or updated becomes available in the database. /// This is not guaranteed to run on the update thread. /// - public Action ItemUpdated { get; set; } + public event Action ItemUpdated; /// /// Fired when a is removed from the database. /// This is not guaranteed to run on the update thread. /// - public Action ItemRemoved { get; set; } + public event Action ItemRemoved; public virtual IEnumerable HandledExtensions => new[] { @".zip" }; diff --git a/osu.Game/Database/IModelDownloader.cs b/osu.Game/Database/IModelDownloader.cs index d8e3dad8b5..81fba14244 100644 --- a/osu.Game/Database/IModelDownloader.cs +++ b/osu.Game/Database/IModelDownloader.cs @@ -17,13 +17,13 @@ namespace osu.Game.Database /// Fired when a download begins. /// This is NOT run on the update thread and should be scheduled. /// - Action> DownloadBegan { get; set; } + event Action> DownloadBegan; /// /// Fired when a download is interrupted, either due to user cancellation or failure. /// This is NOT run on the update thread and should be scheduled. /// - Action> DownloadFailed { get; set; } + event Action> DownloadFailed; /// /// Begin a download for the requested . diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index c9257f5877..15ad455f21 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -19,12 +19,12 @@ namespace osu.Game.Database /// /// Fired when an item is updated. /// - Action ItemUpdated { get; set; } + event Action ItemUpdated; /// /// Fired when an item is removed. /// - Action ItemRemoved { get; set; } + event Action ItemRemoved; /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 3684dea456..3c1f181f24 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -19,9 +19,9 @@ namespace osu.Game.Database { public Action PostNotification { protected get; set; } - public Action> DownloadBegan { get; set; } + public event Action> DownloadBegan; - public Action> DownloadFailed { get; set; } + public event Action> DownloadFailed; private readonly IModelImporter importer; private readonly IAPIProvider api; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 0cb5ae2236..9b4216084e 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -246,16 +246,16 @@ namespace osu.Game.Scoring #region Implementation of IModelManager - public Action ItemUpdated + public event Action ItemUpdated { - get => scoreModelManager.ItemUpdated; - set => scoreModelManager.ItemUpdated = value; + add => scoreModelManager.ItemUpdated += value; + remove => scoreModelManager.ItemUpdated -= value; } - public Action ItemRemoved + public event Action ItemRemoved { - get => scoreModelManager.ItemRemoved; - set => scoreModelManager.ItemRemoved = value; + add => scoreModelManager.ItemRemoved += value; + remove => scoreModelManager.ItemRemoved -= value; } public Task ImportFromStableAsync(StableStorage stableStorage) @@ -358,16 +358,16 @@ namespace osu.Game.Scoring #region Implementation of IModelDownloader - public Action> DownloadBegan + public event Action> DownloadBegan { - get => scoreModelDownloader.DownloadBegan; - set => scoreModelDownloader.DownloadBegan = value; + add => scoreModelDownloader.DownloadBegan += value; + remove => scoreModelDownloader.DownloadBegan -= value; } - public Action> DownloadFailed + public event Action> DownloadFailed { - get => scoreModelDownloader.DownloadFailed; - set => scoreModelDownloader.DownloadFailed = value; + add => scoreModelDownloader.DownloadFailed += value; + remove => scoreModelDownloader.DownloadFailed -= value; } public bool Download(IScoreInfo model, bool minimiseDownloadSize) => From f0809801c5784f68c947cda253fa18467cb96564 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 7 Nov 2021 11:34:37 +0900 Subject: [PATCH 013/169] Update remaining actions to events --- osu.Game/Beatmaps/BeatmapManager.cs | 12 ++++++------ osu.Game/Beatmaps/BeatmapModelManager.cs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0f2cf6a730..b330cc2b4c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -100,19 +100,19 @@ namespace osu.Game.Beatmaps /// /// Fired when a single difficulty has been hidden. /// - public Action BeatmapHidden + public event Action BeatmapHidden { - get => beatmapModelManager.BeatmapHidden; - set => beatmapModelManager.BeatmapHidden = value; + add => beatmapModelManager.BeatmapHidden += value; + remove => beatmapModelManager.BeatmapHidden -= value; } /// /// Fired when a single difficulty has been restored. /// - public Action BeatmapRestored + public event Action BeatmapRestored { - get => beatmapModelManager.BeatmapRestored; - set => beatmapModelManager.BeatmapRestored = value; + add => beatmapModelManager.BeatmapRestored += value; + remove => beatmapModelManager.BeatmapRestored -= value; } /// diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 1a78a625d9..eb1bf598a4 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -36,12 +36,12 @@ namespace osu.Game.Beatmaps /// /// Fired when a single difficulty has been hidden. /// - public Action BeatmapHidden; + public event Action BeatmapHidden; /// /// Fired when a single difficulty has been restored. /// - public Action BeatmapRestored; + public event Action BeatmapRestored; /// /// An online lookup queue component which handles populating online beatmap metadata. From 04c2a9cd59a61e3951f1ab36566abbb78bf15ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 17 Oct 2021 20:50:44 +0200 Subject: [PATCH 014/169] Display basic statistics on card hover --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 10 +++ .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 23 ++++++ .../Statistics/BeatmapCardDateStatistic.cs | 55 +++++++++++++ .../Cards/Statistics/BeatmapCardStatistic.cs | 81 +++++++++++++++++++ .../Cards/Statistics/FavouritesStatistic.cs | 23 ++++++ .../Cards/Statistics/PlayCountStatistic.cs | 23 ++++++ 6 files changed, 215 insertions(+) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardDateStatistic.cs create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Statistics/PlayCountStatistic.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 2aeb4ab4e2..b56dcb0a43 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -31,11 +31,20 @@ namespace osu.Game.Tests.Visual.Beatmaps normal.HasVideo = true; normal.HasStoryboard = true; + var withStatistics = CreateAPIBeatmapSet(Ruleset.Value); + withStatistics.Status = BeatmapSetOnlineStatus.Approved; + withStatistics.FavouriteCount = 284_239; + withStatistics.PlayCount = 999_001; + withStatistics.Ranked = DateTimeOffset.Now.AddDays(-45); + var undownloadable = getUndownloadableBeatmapSet(); + undownloadable.LastUpdated = DateTimeOffset.Now.AddYears(-1); var someDifficulties = getManyDifficultiesBeatmapSet(11); someDifficulties.Title = someDifficulties.TitleUnicode = "some difficulties"; someDifficulties.Status = BeatmapSetOnlineStatus.Qualified; + someDifficulties.HasFavourited = true; + someDifficulties.FavouriteCount = 1; var manyDifficulties = getManyDifficultiesBeatmapSet(100); manyDifficulties.Status = BeatmapSetOnlineStatus.Pending; @@ -59,6 +68,7 @@ namespace osu.Game.Tests.Visual.Beatmaps testCases = new[] { normal, + withStatistics, undownloadable, someDifficulties, manyDifficulties, diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 8136ebbd70..5d8f0a93cd 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,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.Statistics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -40,6 +42,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private GridContainer titleContainer; private GridContainer artistContainer; + private FillFlowContainer statisticsContainer; [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -176,6 +179,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); d.AddUserLink(beatmapSet.Author); }), + statisticsContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Alpha = 0, + ChildrenEnumerable = createStatistics() + } } }, new FillFlowContainer @@ -265,6 +277,16 @@ namespace osu.Game.Beatmaps.Drawables.Cards return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); } + private IEnumerable createStatistics() + { + yield return new FavouritesStatistic(beatmapSet); + yield return new PlayCountStatistic(beatmapSet); + + var dateStatistic = BeatmapCardDateStatistic.CreateFor(beatmapSet); + if (dateStatistic != null) + yield return dateStatistic; + } + private void updateState() { float targetWidth = width - height; @@ -275,6 +297,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards mainContentBackground.Dimmed.Value = IsHovered; leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint); + statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardDateStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardDateStatistic.cs new file mode 100644 index 0000000000..8f2c4b538c --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardDateStatistic.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; + +namespace osu.Game.Beatmaps.Drawables.Cards.Statistics +{ + public class BeatmapCardDateStatistic : BeatmapCardStatistic + { + private readonly DateTimeOffset dateTime; + + private BeatmapCardDateStatistic(DateTimeOffset dateTime) + { + this.dateTime = dateTime; + + Icon = FontAwesome.Regular.CheckCircle; + Text = dateTime.ToLocalisableString(@"d MMM yyyy"); + } + + public override object TooltipContent => dateTime; + public override ITooltip GetCustomTooltip() => new DateTooltip(); + + public static BeatmapCardDateStatistic? CreateFor(IBeatmapSetOnlineInfo beatmapSetInfo) + { + var displayDate = displayDateFor(beatmapSetInfo); + + if (displayDate == null) + return null; + + return new BeatmapCardDateStatistic(displayDate.Value); + } + + private static DateTimeOffset? displayDateFor(IBeatmapSetOnlineInfo beatmapSetInfo) + { + // reference: https://github.com/ppy/osu-web/blob/ef432c11719fd1207bec5f9194b04f0033bdf02c/resources/assets/lib/beatmapset-panel.tsx#L36-L44 + switch (beatmapSetInfo.Status) + { + case BeatmapSetOnlineStatus.Ranked: + case BeatmapSetOnlineStatus.Approved: + case BeatmapSetOnlineStatus.Loved: + case BeatmapSetOnlineStatus.Qualified: + return beatmapSetInfo.Ranked; + + default: + return beatmapSetInfo.LastUpdated; + } + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs new file mode 100644 index 0000000000..95d99b9128 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs @@ -0,0 +1,81 @@ +// 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.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Beatmaps.Drawables.Cards.Statistics +{ + /// + /// A single statistic shown on a beatmap card. + /// + public abstract class BeatmapCardStatistic : CompositeDrawable, IHasTooltip, IHasCustomTooltip + { + protected IconUsage Icon + { + get => spriteIcon.Icon; + set => spriteIcon.Icon = value; + } + + protected LocalisableString Text + { + get => spriteText.Text; + set => spriteText.Text = value; + } + + protected LocalisableString TooltipText { get; set; } + + private readonly SpriteIcon spriteIcon; + private readonly OsuSpriteText spriteText; + + protected BeatmapCardStatistic() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + spriteIcon = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(10), + Margin = new MarginPadding { Top = 1 } + }, + spriteText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.Default.With(size: 14) + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + spriteIcon.Colour = colourProvider.Content2; + } + + #region Tooltip implementation + + LocalisableString IHasTooltip.TooltipText => TooltipText; + public virtual ITooltip GetCustomTooltip() => null; + public virtual object TooltipContent => null; + + #endregion + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs new file mode 100644 index 0000000000..7b3286ddcc --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/FavouritesStatistic.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Beatmaps.Drawables.Cards.Statistics +{ + /// + /// Shows the number of favourites that a beatmap set has received. + /// + public class FavouritesStatistic : BeatmapCardStatistic + { + 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")); + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/PlayCountStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/PlayCountStatistic.cs new file mode 100644 index 0000000000..d8f0c36bd9 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/PlayCountStatistic.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Beatmaps.Drawables.Cards.Statistics +{ + /// + /// Shows the number of times the given beatmap set has been played. + /// + public class PlayCountStatistic : BeatmapCardStatistic + { + public PlayCountStatistic(IBeatmapSetOnlineInfo onlineInfo) + { + Icon = FontAwesome.Regular.PlayCircle; + Text = onlineInfo.PlayCount.ToMetric(decimals: 1); + TooltipText = BeatmapsStrings.PanelPlaycount(onlineInfo.PlayCount.ToLocalisableString(@"N0")); + } + } +} From c0b5b0e9091b2f3b3b146b88bffd053ccdd7f370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 17 Oct 2021 20:55:06 +0200 Subject: [PATCH 015/169] Add new statistic types to online info --- osu.Game/Beatmaps/BeatmapSetHypeStatus.cs | 25 +++++++++++++++++++ .../Beatmaps/BeatmapSetNominationStatus.cs | 25 +++++++++++++++++++ osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs | 14 +++++++++++ .../API/Requests/Responses/APIBeatmapSet.cs | 6 +++++ 4 files changed, 70 insertions(+) create mode 100644 osu.Game/Beatmaps/BeatmapSetHypeStatus.cs create mode 100644 osu.Game/Beatmaps/BeatmapSetNominationStatus.cs diff --git a/osu.Game/Beatmaps/BeatmapSetHypeStatus.cs b/osu.Game/Beatmaps/BeatmapSetHypeStatus.cs new file mode 100644 index 0000000000..8a576e396a --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapSetHypeStatus.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Beatmaps +{ + /// + /// Contains information about the current hype status of a beatmap set. + /// + public class BeatmapSetHypeStatus + { + /// + /// The current number of hypes that the set has received. + /// + [JsonProperty(@"current")] + public int Current { get; set; } + + /// + /// The number of hypes required so that the set is eligible for nomination. + /// + [JsonProperty(@"required")] + public int Required { get; set; } + } +} diff --git a/osu.Game/Beatmaps/BeatmapSetNominationStatus.cs b/osu.Game/Beatmaps/BeatmapSetNominationStatus.cs new file mode 100644 index 0000000000..6a19616a97 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapSetNominationStatus.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Beatmaps +{ + /// + /// Contains information about the current nomination status of a beatmap set. + /// + public class BeatmapSetNominationStatus + { + /// + /// The current number of nominations that the set has received. + /// + [JsonProperty(@"current")] + public int Current { get; set; } + + /// + /// The number of nominations required so that the map is eligible for qualification. + /// + [JsonProperty(@"required")] + public int Required { get; set; } + } +} diff --git a/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs index 6def6ec21d..2982cf9c3a 100644 --- a/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapSetOnlineInfo.cs @@ -102,5 +102,19 @@ namespace osu.Game.Beatmaps /// Total vote counts of user ratings on a scale of 0..10 where 0 is unused (probably will be fixed at API?). /// int[]? Ratings { get; } + + /// + /// Contains the current hype status of the beatmap set. + /// Non-null only for , , and sets. + /// + /// + /// See: https://github.com/ppy/osu-web/blob/93930cd02cfbd49724929912597c727c9fbadcd1/app/Models/Beatmapset.php#L155 + /// + BeatmapSetHypeStatus? HypeStatus { get; } + + /// + /// Contains the current nomination status of the beatmap set. + /// + BeatmapSetNominationStatus? NominationStatus { get; } } } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 47536879b2..db4c45d03f 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -62,6 +62,12 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"track_id")] public int? TrackId { get; set; } + [JsonProperty(@"hype")] + public BeatmapSetHypeStatus? HypeStatus { get; set; } + + [JsonProperty(@"nominations_summary")] + public BeatmapSetNominationStatus? NominationStatus { get; set; } + public string Title { get; set; } = string.Empty; [JsonProperty("title_unicode")] From 1f405a7e71d399988bac3c19b2c9cd14db502d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 17 Oct 2021 21:05:26 +0200 Subject: [PATCH 016/169] Display extended statistics on card hover --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 10 +++++++++ .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 6 +++++ .../Cards/Statistics/HypesStatistic.cs | 22 +++++++++++++++++++ .../Cards/Statistics/NominationsStatistic.cs | 22 +++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs create mode 100644 osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index b56dcb0a43..0f324ed846 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -36,6 +36,16 @@ namespace osu.Game.Tests.Visual.Beatmaps withStatistics.FavouriteCount = 284_239; withStatistics.PlayCount = 999_001; withStatistics.Ranked = DateTimeOffset.Now.AddDays(-45); + withStatistics.HypeStatus = new BeatmapSetHypeStatus + { + Current = 34, + Required = 5 + }; + withStatistics.NominationStatus = new BeatmapSetNominationStatus + { + Current = 1, + Required = 2 + }; var undownloadable = getUndownloadableBeatmapSet(); undownloadable.LastUpdated = DateTimeOffset.Now.AddYears(-1); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 5d8f0a93cd..7a3d212716 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -279,6 +279,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards private IEnumerable createStatistics() { + if (beatmapSet.HypeStatus != null) + yield return new HypesStatistic(beatmapSet.HypeStatus); + + if (beatmapSet.NominationStatus != null) + yield return new NominationsStatistic(beatmapSet.NominationStatus); + yield return new FavouritesStatistic(beatmapSet); yield return new PlayCountStatistic(beatmapSet); diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs new file mode 100644 index 0000000000..3fe31c7a41 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.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.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Beatmaps.Drawables.Cards.Statistics +{ + /// + /// Shows the number of current hypes that a map has received, as well as the number of hypes required for nomination. + /// + public class HypesStatistic : BeatmapCardStatistic + { + public HypesStatistic(BeatmapSetHypeStatus hypeStatus) + { + Icon = FontAwesome.Solid.Bullhorn; + Text = hypeStatus.Current.ToLocalisableString(); + TooltipText = BeatmapsStrings.HypeRequiredText(hypeStatus.Current.ToLocalisableString(), hypeStatus.Required.ToLocalisableString()); + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs new file mode 100644 index 0000000000..f09269a615 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.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.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Beatmaps.Drawables.Cards.Statistics +{ + /// + /// Shows the number of current nominations that a map has received, as well as the number of nominations required for qualification. + /// + public class NominationsStatistic : BeatmapCardStatistic + { + public NominationsStatistic(BeatmapSetNominationStatus nominationStatus) + { + Icon = FontAwesome.Solid.ThumbsUp; + Text = nominationStatus.Current.ToLocalisableString(); + TooltipText = BeatmapsStrings.NominationsRequiredText(nominationStatus.Current.ToLocalisableString(), nominationStatus.Required.ToLocalisableString()); + } + } +} From 0e09edfce21999f6a06f5830067419b31f400161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 Nov 2021 21:20:21 +0100 Subject: [PATCH 017/169] Do not show nomination info if hype info not present to match web --- osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs | 5 +++++ osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 0f324ed846..b298a29c80 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -55,6 +55,11 @@ namespace osu.Game.Tests.Visual.Beatmaps someDifficulties.Status = BeatmapSetOnlineStatus.Qualified; someDifficulties.HasFavourited = true; someDifficulties.FavouriteCount = 1; + someDifficulties.NominationStatus = new BeatmapSetNominationStatus + { + Current = 2, + Required = 2 + }; var manyDifficulties = getManyDifficultiesBeatmapSet(100); manyDifficulties.Status = BeatmapSetOnlineStatus.Pending; diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 7a3d212716..c53c1abd8d 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -282,7 +282,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards if (beatmapSet.HypeStatus != null) yield return new HypesStatistic(beatmapSet.HypeStatus); - if (beatmapSet.NominationStatus != null) + // web does not show nominations unless hypes are also present. + // see: https://github.com/ppy/osu-web/blob/8ed7d071fd1d3eaa7e43cf0e4ff55ca2fef9c07c/resources/assets/lib/beatmapset-panel.tsx#L443 + if (beatmapSet.HypeStatus != null && beatmapSet.NominationStatus != null) yield return new NominationsStatistic(beatmapSet.NominationStatus); yield return new FavouritesStatistic(beatmapSet); From cc0bcf6b2c2187a8a5adaeb5e360f2a4d6705569 Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Sun, 7 Nov 2021 17:44:50 -0700 Subject: [PATCH 018/169] 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 019/169] 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 fb495d77ce61ed833b26f8dc96f4b21d00f3670c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 13:22:57 +0900 Subject: [PATCH 020/169] Implement `TooltipText` directly --- .../Drawables/Cards/Statistics/BeatmapCardStatistic.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs index 95d99b9128..f46926284f 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs @@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics set => spriteText.Text = value; } - protected LocalisableString TooltipText { get; set; } + public LocalisableString TooltipText { get; protected set; } private readonly SpriteIcon spriteIcon; private readonly OsuSpriteText spriteText; @@ -72,7 +72,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics #region Tooltip implementation - LocalisableString IHasTooltip.TooltipText => TooltipText; public virtual ITooltip GetCustomTooltip() => null; public virtual object TooltipContent => null; From 43f87844e24c35357c459ec46216fca89e50d606 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 13:27:04 +0900 Subject: [PATCH 021/169] Add text to test cards to better explain their purpose --- osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index b298a29c80..55e51a93f4 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Beatmaps normal.HasStoryboard = true; var withStatistics = CreateAPIBeatmapSet(Ruleset.Value); + withStatistics.Title = withStatistics.TitleUnicode = "play favourite stats"; withStatistics.Status = BeatmapSetOnlineStatus.Approved; withStatistics.FavouriteCount = 284_239; withStatistics.PlayCount = 999_001; @@ -51,6 +52,7 @@ namespace osu.Game.Tests.Visual.Beatmaps undownloadable.LastUpdated = DateTimeOffset.Now.AddYears(-1); var someDifficulties = getManyDifficultiesBeatmapSet(11); + someDifficulties.Title = someDifficulties.TitleUnicode = "favourited"; someDifficulties.Title = someDifficulties.TitleUnicode = "some difficulties"; someDifficulties.Status = BeatmapSetOnlineStatus.Qualified; someDifficulties.HasFavourited = true; @@ -65,12 +67,15 @@ namespace osu.Game.Tests.Visual.Beatmaps manyDifficulties.Status = BeatmapSetOnlineStatus.Pending; var explicitMap = CreateAPIBeatmapSet(Ruleset.Value); + explicitMap.Title = someDifficulties.TitleUnicode = "explicit beatmap"; explicitMap.HasExplicitContent = true; var featuredMap = CreateAPIBeatmapSet(Ruleset.Value); + featuredMap.Title = someDifficulties.TitleUnicode = "featured artist beatmap"; featuredMap.TrackId = 1; var explicitFeaturedMap = CreateAPIBeatmapSet(Ruleset.Value); + explicitFeaturedMap.Title = someDifficulties.TitleUnicode = "explicit featured artist"; explicitFeaturedMap.HasExplicitContent = true; explicitFeaturedMap.TrackId = 2; From 6b6dd93e9e75e38bd73bd40b5a943f07c19b57d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 14:17:47 +0900 Subject: [PATCH 022/169] Fix `LinkFlowContainer` not creating user links supporting full `IUser` specification --- .../Graphics/Containers/LinkFlowContainer.cs | 10 +++---- osu.Game/Online/Chat/MessageFormatter.cs | 8 ++--- osu.Game/OsuGame.cs | 29 ++++++++++++------- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- .../Sections/Recent/DrawableRecentActivity.cs | 2 +- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 41b7df228e..1d286d3487 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Graphics.Containers AddText(text[previousLinkEnd..link.Index]); string displayText = text.Substring(link.Index, link.Length); - string linkArgument = link.Argument; + object linkArgument = link.Argument; string tooltip = displayText == link.Url ? null : link.Url; AddLink(displayText, link.Action, linkArgument, tooltip); @@ -62,16 +62,16 @@ namespace osu.Game.Graphics.Containers public void AddLink(LocalisableString text, Action action, string tooltipText = null, Action creationParameters = null) => createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.Custom, string.Empty), tooltipText, action); - public void AddLink(LocalisableString text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) + public void AddLink(LocalisableString text, LinkAction action, object argument, string tooltipText = null, Action creationParameters = null) => createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(action, argument), tooltipText); - public void AddLink(IEnumerable text, LinkAction action, string linkArgument, string tooltipText = null) + public void AddLink(IEnumerable text, LinkAction action, object linkArgument, string tooltipText = null) { createLink(new TextPartManual(text), new LinkDetails(action, linkArgument), tooltipText); } public void AddUserLink(IUser user, Action creationParameters = null) - => createLink(CreateChunkFor(user.Username, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.OnlineID.ToString()), "view profile"); + => createLink(CreateChunkFor(user.Username, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user), "view profile"); private void createLink(ITextPart textPart, LinkDetails link, LocalisableString tooltipText, Action action = null) { @@ -83,7 +83,7 @@ namespace osu.Game.Graphics.Containers game.HandleLink(link); // fallback to handle cases where OsuGame is not available, ie. tournament client. else if (link.Action == LinkAction.External) - host.OpenUrlExternally(link.Argument); + host.OpenUrlExternally(link.Argument.ToString()); }; AddPart(new TextLink(textPart, tooltipText, onClickAction)); diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 5e0f66443b..92911f0f51 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -320,9 +320,9 @@ namespace osu.Game.Online.Chat { public readonly LinkAction Action; - public readonly string Argument; + public readonly object Argument; - public LinkDetails(LinkAction action, string argument) + public LinkDetails(LinkAction action, object argument) { Action = action; Argument = argument; @@ -351,9 +351,9 @@ namespace osu.Game.Online.Chat public int Index; public int Length; public LinkAction Action; - public string Argument; + public object Argument; - public Link(string url, int startIndex, int length, LinkAction action, string argument) + public Link(string url, int startIndex, int length, LinkAction action, object argument) { Url = url; Index = startIndex; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ea8682e696..bf757a153f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -289,25 +289,27 @@ namespace osu.Game /// The link to load. public void HandleLink(LinkDetails link) => Schedule(() => { + string argString = link.Argument.ToString(); + switch (link.Action) { case LinkAction.OpenBeatmap: // TODO: proper query params handling - if (int.TryParse(link.Argument.Contains('?') ? link.Argument.Split('?')[0] : link.Argument, out int beatmapId)) + if (int.TryParse(argString.Contains('?') ? argString.Split('?')[0] : argString, out int beatmapId)) ShowBeatmap(beatmapId); break; case LinkAction.OpenBeatmapSet: - if (int.TryParse(link.Argument, out int setId)) + if (int.TryParse(argString, out int setId)) ShowBeatmapSet(setId); break; case LinkAction.OpenChannel: - ShowChannel(link.Argument); + ShowChannel(argString); break; case LinkAction.SearchBeatmapSet: - SearchBeatmapSet(link.Argument); + SearchBeatmapSet(argString); break; case LinkAction.OpenEditorTimestamp: @@ -321,26 +323,31 @@ namespace osu.Game break; case LinkAction.External: - OpenUrlExternally(link.Argument); + OpenUrlExternally(argString); break; case LinkAction.OpenUserProfile: - ShowUser(int.TryParse(link.Argument, out int userId) - ? new APIUser { Id = userId } - : new APIUser { Username = link.Argument }); + if (!(link.Argument is IUser user)) + { + user = int.TryParse(argString, out int userId) + ? new APIUser { Id = userId } + : new APIUser { Username = argString }; + } + + ShowUser(user); break; case LinkAction.OpenWiki: - ShowWiki(link.Argument); + ShowWiki(argString); break; case LinkAction.OpenChangelog: - if (string.IsNullOrEmpty(link.Argument)) + if (string.IsNullOrEmpty(argString)) ShowChangelogListing(); else { - string[] changelogArgs = link.Argument.Split("/"); + string[] changelogArgs = argString.Split("/"); ShowChangelogBuild(changelogArgs[0], changelogArgs[1]); } diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 97cd913b56..87d1b1a3ad 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -209,7 +209,7 @@ namespace osu.Game.Overlays.Chat username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); // remove non-existent channels from the link list - message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument) != true); + message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true); ContentFlow.Clear(); ContentFlow.AddLinks(message.DisplayContent, message.Links); diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index 49b46f7e7a..cb8dae0bbc 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -216,7 +216,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent private void addBeatmapsetLink() => content.AddLink(activity.Beatmapset?.Title, LinkAction.OpenBeatmapSet, getLinkArgument(activity.Beatmapset?.Url), creationParameters: t => t.Font = getLinkFont()); - private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.APIEndpointUrl}{url}").Argument; + private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.APIEndpointUrl}{url}").Argument.ToString(); private FontUsage getLinkFont(FontWeight fontWeight = FontWeight.Regular) => OsuFont.GetFont(size: font_size, weight: fontWeight, italics: true); From c3069ad002cb4e4a298b0a0da1e9419a6bb3c08a Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 8 Nov 2021 06:55:26 +0100 Subject: [PATCH 023/169] Change to use `ReadableKeyCombinationProvider` Changes all usages of `KeyCombination.ReadableString()` to `ReadableKeyCombinationProvider.GetReadableString()`. Subscribing to `KeymapChanged` is only required in `KeyButton`. All other places query `GetReadableString()` every time. --- .../Database/TestRealmKeyBindingStore.cs | 3 +- osu.Game/Input/RealmKeyBindingStore.cs | 7 ++-- osu.Game/OsuGameBase.cs | 4 +-- .../Settings/Sections/Input/KeyBindingRow.cs | 32 +++++++++++++++++-- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 6 +++- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index f10b11733e..860828ae81 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Platform; using osu.Game.Database; @@ -33,7 +34,7 @@ namespace osu.Game.Tests.Database storage = new NativeStorage(directory.FullName); realmContextFactory = new RealmContextFactory(storage, "test"); - keyBindingStore = new RealmKeyBindingStore(realmContextFactory); + keyBindingStore = new RealmKeyBindingStore(realmContextFactory, new ReadableKeyCombinationProvider()); } [Test] diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index c65e36e478..677a7baf62 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Game.Database; using osu.Game.Input.Bindings; @@ -16,10 +17,12 @@ namespace osu.Game.Input public class RealmKeyBindingStore { private readonly RealmContextFactory realmFactory; + private readonly ReadableKeyCombinationProvider readableKeyCombinationProvider; - public RealmKeyBindingStore(RealmContextFactory realmFactory) + public RealmKeyBindingStore(RealmContextFactory realmFactory, ReadableKeyCombinationProvider readableKeyCombinationProvider) { this.realmFactory = realmFactory; + this.readableKeyCombinationProvider = readableKeyCombinationProvider; } /// @@ -35,7 +38,7 @@ namespace osu.Game.Input { foreach (var action in context.All().Where(b => b.RulesetID == null && (GlobalAction)b.ActionInt == globalAction)) { - string str = action.KeyCombination.ReadableString(); + string str = readableKeyCombinationProvider.GetReadableString(action.KeyCombination); // even if found, the readable string may be empty for an unbound action. if (str.Length > 0) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f6ec22a536..f23a0433e6 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -167,7 +167,7 @@ namespace osu.Game } [BackgroundDependencyLoader] - private void load() + private void load(ReadableKeyCombinationProvider readableKeyCombinationProvider) { try { @@ -316,7 +316,7 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); - KeyBindingStore = new RealmKeyBindingStore(realmFactory); + KeyBindingStore = new RealmKeyBindingStore(realmFactory, readableKeyCombinationProvider); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); dependencies.Cache(globalBindings); diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index f44f02d0ed..758f93692e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Database; @@ -57,13 +58,16 @@ namespace osu.Game.Overlays.Settings.Sections.Input public bool FilteringActive { get; set; } + [Resolved] + private ReadableKeyCombinationProvider readableKeyCombinationProvider { get; set; } + private OsuSpriteText text; private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; private Bindable isDefault { get; } = new BindableBool(true); - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString()); + public IEnumerable FilterTerms => bindings.Select(b => readableKeyCombinationProvider.GetReadableString(b.KeyCombination)).Prepend(text.Text.ToString()); public KeyBindingRow(object action, List bindings) { @@ -422,6 +426,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input [Resolved] private OverlayColourProvider colourProvider { get; set; } + [Resolved] + private ReadableKeyCombinationProvider readableKeyCombinationProvider { get; set; } + private bool isBinding; public bool IsBinding @@ -470,12 +477,19 @@ namespace osu.Game.Overlays.Settings.Sections.Input Margin = new MarginPadding(5), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = keyBinding.KeyCombination.ReadableString(), }, new HoverSounds() }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + readableKeyCombinationProvider.KeymapChanged += updateKeyCombinationText; + updateKeyCombinationText(); + } + [BackgroundDependencyLoader] private void load() { @@ -508,13 +522,25 @@ namespace osu.Game.Overlays.Settings.Sections.Input } } + private void updateKeyCombinationText() + { + Text.Text = readableKeyCombinationProvider.GetReadableString(KeyBinding.KeyCombination); + } + public void UpdateKeyCombination(KeyCombination newCombination) { if (KeyBinding.RulesetID != null && !RealmKeyBindingStore.CheckValidForGameplay(newCombination)) return; KeyBinding.KeyCombination = newCombination; - Text.Text = KeyBinding.KeyCombination.ReadableString(); + updateKeyCombinationText(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + readableKeyCombinationProvider.KeymapChanged -= updateKeyCombinationText; } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index ab37b3b355..0886bb8f8c 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Database; @@ -39,6 +40,9 @@ namespace osu.Game.Overlays.Toolbar [Resolved] private TextureStore textures { get; set; } + [Resolved] + private ReadableKeyCombinationProvider readableKeyCombinationProvider { get; set; } + public void SetIcon(string texture) => SetIcon(new Sprite { @@ -207,7 +211,7 @@ namespace osu.Game.Overlays.Toolbar if (realmKeyBinding != null) { - string keyBindingString = realmKeyBinding.KeyCombination.ReadableString(); + string keyBindingString = readableKeyCombinationProvider.GetReadableString(realmKeyBinding.KeyCombination); if (!string.IsNullOrEmpty(keyBindingString)) keyBindingTooltip.Text = $" ({keyBindingString})"; From cc286f165d9e7a693377acd8f5347d7d13e5bb31 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 8 Nov 2021 06:56:57 +0100 Subject: [PATCH 024/169] Change font to default Non-english letters look tiny with size 10 and don't fit into the look. --- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 758f93692e..fe0905d76f 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -473,7 +473,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, Text = new OsuSpriteText { - Font = OsuFont.Numeric.With(size: 10), + Font = OsuFont.Default, Margin = new MarginPadding(5), Anchor = Anchor.Centre, Origin = Anchor.Centre, From 72b4afdea688bebc8d2b7eefd72815c8adf491f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Oct 2021 16:51:34 +0900 Subject: [PATCH 025/169] Move `RealmRulesetStore` initial work off incorrect thread --- osu.Game/Stores/RealmRulesetStore.cs | 117 ++++++++++++++------------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/osu.Game/Stores/RealmRulesetStore.cs b/osu.Game/Stores/RealmRulesetStore.cs index e9c04f652d..0d2cddf874 100644 --- a/osu.Game/Stores/RealmRulesetStore.cs +++ b/osu.Game/Stores/RealmRulesetStore.cs @@ -102,75 +102,78 @@ namespace osu.Game.Stores private void addMissingRulesets() { - realmFactory.Context.Write(realm => + using (var context = realmFactory.CreateContext()) { - var rulesets = realm.All(); - - List instances = loadedAssemblies.Values - .Select(r => Activator.CreateInstance(r) as Ruleset) - .Where(r => r != null) - .Select(r => r.AsNonNull()) - .ToList(); - - // add all legacy rulesets first to ensure they have exclusive choice of primary key. - foreach (var r in instances.Where(r => r is ILegacyRuleset)) + context.Write(realm => { - if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.ID) == null) - realm.Add(new RealmRuleset(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.ID)); - } + var rulesets = realm.All(); - // add any other rulesets which have assemblies present but are not yet in the database. - foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) - { - if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) + List instances = loadedAssemblies.Values + .Select(r => Activator.CreateInstance(r) as Ruleset) + .Where(r => r != null) + .Select(r => r.AsNonNull()) + .ToList(); + + // add all legacy rulesets first to ensure they have exclusive choice of primary key. + foreach (var r in instances.Where(r => r is ILegacyRuleset)) { - var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); - - if (existingSameShortName != null) - { - // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. - // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. - // in such cases, update the instantiation info of the existing entry to point to the new one. - existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; - } - else + if (realm.All().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.ID) == null) realm.Add(new RealmRuleset(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.ID)); } - } - List detachedRulesets = new List(); - - // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. - foreach (var r in rulesets) - { - try + // add any other rulesets which have assemblies present but are not yet in the database. + foreach (var r in instances.Where(r => !(r is ILegacyRuleset))) { - var type = Type.GetType(r.InstantiationInfo); + if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null) + { + var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName); - if (type == null) - throw new InvalidOperationException(@"Type resolution failure."); - - var rInstance = (Activator.CreateInstance(type) as Ruleset)?.RulesetInfo; - - if (rInstance == null) - throw new InvalidOperationException(@"Instantiation failure."); - - r.Name = rInstance.Name; - r.ShortName = rInstance.ShortName; - r.InstantiationInfo = rInstance.InstantiationInfo; - r.Available = true; - - detachedRulesets.Add(r.Clone()); + if (existingSameShortName != null) + { + // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName. + // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one. + // in such cases, update the instantiation info of the existing entry to point to the new one. + existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo; + } + else + realm.Add(new RealmRuleset(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.ID)); + } } - catch (Exception ex) + + List detachedRulesets = new List(); + + // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage. + foreach (var r in rulesets) { - r.Available = false; - Logger.Log($"Could not load ruleset {r}: {ex.Message}"); - } - } + try + { + var type = Type.GetType(r.InstantiationInfo); - availableRulesets.AddRange(detachedRulesets); - }); + if (type == null) + throw new InvalidOperationException(@"Type resolution failure."); + + var rInstance = (Activator.CreateInstance(type) as Ruleset)?.RulesetInfo; + + if (rInstance == null) + throw new InvalidOperationException(@"Instantiation failure."); + + r.Name = rInstance.Name; + r.ShortName = rInstance.ShortName; + r.InstantiationInfo = rInstance.InstantiationInfo; + r.Available = true; + + detachedRulesets.Add(r.Clone()); + } + catch (Exception ex) + { + r.Available = false; + Logger.Log($"Could not load ruleset {r}: {ex.Message}"); + } + } + + availableRulesets.AddRange(detachedRulesets); + }); + } } private void loadFromAppDomain() From 413f98999a06f54b7171ad299f401911b4865cf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Oct 2021 16:33:09 +0900 Subject: [PATCH 026/169] Start initialising `RealmRulesetStore` on startup --- osu.Game/OsuGameBase.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f6ec22a536..c1cc2a7f25 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -40,6 +40,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Skinning; +using osu.Game.Stores; using osu.Game.Utils; using RuntimeInfo = osu.Framework.RuntimeInfo; @@ -237,6 +238,11 @@ namespace osu.Game dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); + // the following realm components are not actively used yet, but initialised and kept up to date for initial testing. + var realmRulesetStore = new RealmRulesetStore(realmFactory, Storage); + + dependencies.Cache(realmRulesetStore); + // this should likely be moved to ArchiveModelManager when another case appears where it is necessary // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete. From ea0715cce4083ea896f58c1d7763a893b903d9d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 16:35:05 +0900 Subject: [PATCH 027/169] Add missing dispose call --- osu.Game/OsuGameBase.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3f7339c031..7911e843d1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -161,6 +161,8 @@ namespace osu.Game private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST); + private RealmRulesetStore realmRulesetStore; + public OsuGameBase() { UseDevelopmentServer = DebugUtils.IsDebugBuild; @@ -233,7 +235,7 @@ namespace osu.Game dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); // the following realm components are not actively used yet, but initialised and kept up to date for initial testing. - var realmRulesetStore = new RealmRulesetStore(realmFactory, Storage); + realmRulesetStore = new RealmRulesetStore(realmFactory, Storage); dependencies.Cache(realmRulesetStore); @@ -518,6 +520,8 @@ namespace osu.Game LocalConfig?.Dispose(); contextFactory?.FlushConnections(); + + realmRulesetStore?.Dispose(); realmFactory?.Dispose(); } } From c1e7fcd2d744edc62311bd8b4e9084888cab8af2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 18:15:51 +0900 Subject: [PATCH 028/169] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 4f9f83f199..752eb160ed 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7811de5764..df3c9b355a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 9d95b7c765..1852957e87 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 82f24b0502272f48520f259100720c1356c16d55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 18:16:06 +0900 Subject: [PATCH 029/169] Revert "Change font to default" This reverts commit cc286f165d9e7a693377acd8f5347d7d13e5bb31. --- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index fe0905d76f..758f93692e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -473,7 +473,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, Text = new OsuSpriteText { - Font = OsuFont.Default, + Font = OsuFont.Numeric.With(size: 10), Margin = new MarginPadding(5), Anchor = Anchor.Centre, Origin = Anchor.Centre, From f5842e75875feaf736d376dd33dfe5e47b8d7acd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 18:24:37 +0900 Subject: [PATCH 030/169] Shorten variable names --- osu.Game/Input/RealmKeyBindingStore.cs | 8 ++++---- osu.Game/OsuGameBase.cs | 4 ++-- .../Settings/Sections/Input/KeyBindingRow.cs | 12 ++++++------ osu.Game/Overlays/Toolbar/ToolbarButton.cs | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 677a7baf62..046969579c 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -17,12 +17,12 @@ namespace osu.Game.Input public class RealmKeyBindingStore { private readonly RealmContextFactory realmFactory; - private readonly ReadableKeyCombinationProvider readableKeyCombinationProvider; + private readonly ReadableKeyCombinationProvider keyCombinationProvider; - public RealmKeyBindingStore(RealmContextFactory realmFactory, ReadableKeyCombinationProvider readableKeyCombinationProvider) + public RealmKeyBindingStore(RealmContextFactory realmFactory, ReadableKeyCombinationProvider keyCombinationProvider) { this.realmFactory = realmFactory; - this.readableKeyCombinationProvider = readableKeyCombinationProvider; + this.keyCombinationProvider = keyCombinationProvider; } /// @@ -38,7 +38,7 @@ namespace osu.Game.Input { foreach (var action in context.All().Where(b => b.RulesetID == null && (GlobalAction)b.ActionInt == globalAction)) { - string str = readableKeyCombinationProvider.GetReadableString(action.KeyCombination); + string str = keyCombinationProvider.GetReadableString(action.KeyCombination); // even if found, the readable string may be empty for an unbound action. if (str.Length > 0) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0dc511c361..10166daf00 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -170,7 +170,7 @@ namespace osu.Game } [BackgroundDependencyLoader] - private void load(ReadableKeyCombinationProvider readableKeyCombinationProvider) + private void load(ReadableKeyCombinationProvider keyCombinationProvider) { try { @@ -309,7 +309,7 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); - KeyBindingStore = new RealmKeyBindingStore(realmFactory, readableKeyCombinationProvider); + KeyBindingStore = new RealmKeyBindingStore(realmFactory, keyCombinationProvider); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); dependencies.Cache(globalBindings); diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 758f93692e..f9856f30ce 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input public bool FilteringActive { get; set; } [Resolved] - private ReadableKeyCombinationProvider readableKeyCombinationProvider { get; set; } + private ReadableKeyCombinationProvider keyCombinationProvider { get; set; } private OsuSpriteText text; private FillFlowContainer cancelAndClearButtons; @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private Bindable isDefault { get; } = new BindableBool(true); - public IEnumerable FilterTerms => bindings.Select(b => readableKeyCombinationProvider.GetReadableString(b.KeyCombination)).Prepend(text.Text.ToString()); + public IEnumerable FilterTerms => bindings.Select(b => keyCombinationProvider.GetReadableString(b.KeyCombination)).Prepend(text.Text.ToString()); public KeyBindingRow(object action, List bindings) { @@ -427,7 +427,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private OverlayColourProvider colourProvider { get; set; } [Resolved] - private ReadableKeyCombinationProvider readableKeyCombinationProvider { get; set; } + private ReadableKeyCombinationProvider keyCombinationProvider { get; set; } private bool isBinding; @@ -486,7 +486,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { base.LoadComplete(); - readableKeyCombinationProvider.KeymapChanged += updateKeyCombinationText; + keyCombinationProvider.KeymapChanged += updateKeyCombinationText; updateKeyCombinationText(); } @@ -524,7 +524,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateKeyCombinationText() { - Text.Text = readableKeyCombinationProvider.GetReadableString(KeyBinding.KeyCombination); + Text.Text = keyCombinationProvider.GetReadableString(KeyBinding.KeyCombination); } public void UpdateKeyCombination(KeyCombination newCombination) @@ -540,7 +540,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { base.Dispose(isDisposing); - readableKeyCombinationProvider.KeymapChanged -= updateKeyCombinationText; + keyCombinationProvider.KeymapChanged -= updateKeyCombinationText; } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 0886bb8f8c..b2252a5575 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Toolbar private TextureStore textures { get; set; } [Resolved] - private ReadableKeyCombinationProvider readableKeyCombinationProvider { get; set; } + private ReadableKeyCombinationProvider keyCombinationProvider { get; set; } public void SetIcon(string texture) => SetIcon(new Sprite @@ -211,7 +211,7 @@ namespace osu.Game.Overlays.Toolbar if (realmKeyBinding != null) { - string keyBindingString = readableKeyCombinationProvider.GetReadableString(realmKeyBinding.KeyCombination); + string keyBindingString = keyCombinationProvider.GetReadableString(realmKeyBinding.KeyCombination); if (!string.IsNullOrEmpty(keyBindingString)) keyBindingTooltip.Text = $" ({keyBindingString})"; From b4225804ed5c44196db5df5dd7a07552f6e24561 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 18:26:12 +0900 Subject: [PATCH 031/169] Add missing null check --- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index f9856f30ce..40bf74cbf1 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -540,7 +540,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { base.Dispose(isDisposing); - keyCombinationProvider.KeymapChanged -= updateKeyCombinationText; + if (keyCombinationProvider != null) + keyCombinationProvider.KeymapChanged -= updateKeyCombinationText; } } } From 49c26a465c75aabd628561e6a117243fe2fda6d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 18:29:11 +0900 Subject: [PATCH 032/169] Debounce and schedule updates to key combinations --- .../Settings/Sections/Input/KeyBindingRow.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 40bf74cbf1..e6472dffeb 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -522,11 +522,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input } } - private void updateKeyCombinationText() - { - Text.Text = keyCombinationProvider.GetReadableString(KeyBinding.KeyCombination); - } - public void UpdateKeyCombination(KeyCombination newCombination) { if (KeyBinding.RulesetID != null && !RealmKeyBindingStore.CheckValidForGameplay(newCombination)) @@ -536,6 +531,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input updateKeyCombinationText(); } + private void updateKeyCombinationText() + { + Scheduler.AddOnce(updateText); + + void updateText() => Text.Text = keyCombinationProvider.GetReadableString(KeyBinding.KeyCombination); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); 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 033/169] 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 034/169] 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 035/169] 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 036/169] 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 037/169] 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 038/169] 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 039/169] 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 040/169] 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 041/169] 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 042/169] 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 043/169] 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 044/169] 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 e6f39c4cada3e180d524be7c5fc0a8d3e7e6b206 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 8 Nov 2021 21:38:01 +0100 Subject: [PATCH 045/169] Fix settings header text overflowing with some locales. --- osu.Game/Overlays/Settings/SettingsHeader.cs | 43 ++++++++------------ 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index 69b7b69a29..8d6e93be9d 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Settings { @@ -29,36 +29,27 @@ namespace osu.Game.Overlays.Settings Children = new Drawable[] { - new FillFlowContainer + new OsuTextFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] + Margin = new MarginPadding { - new OsuSpriteText - { - Text = heading, - Font = OsuFont.TorusAlternate.With(size: 40), - Margin = new MarginPadding - { - Left = SettingsPanel.CONTENT_MARGINS, - Top = Toolbar.Toolbar.TOOLTIP_HEIGHT - }, - }, - new OsuSpriteText - { - Colour = colourProvider.Content2, - Text = subheading, - Font = OsuFont.GetFont(size: 18), - Margin = new MarginPadding - { - Left = SettingsPanel.CONTENT_MARGINS, - Bottom = 30 - }, - }, + Left = SettingsPanel.CONTENT_MARGINS, + Top = Toolbar.Toolbar.TOOLTIP_HEIGHT, + Bottom = 30 } - } + }.With(flow => + { + flow.AddText(heading, header => header.Font = OsuFont.TorusAlternate.With(size: 40)); + flow.NewLine(); + flow.AddText(subheading, subheader => + { + subheader.Colour = colourProvider.Content2; + subheader.Font = OsuFont.GetFont(size: 18); + }); + + }) }; } } From b7e7b0f850766a568e1e08aa985826402f27d1cb Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 8 Nov 2021 21:42:51 +0100 Subject: [PATCH 046/169] Trim whitespace. --- osu.Game/Overlays/Settings/SettingsHeader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index 8d6e93be9d..53b3895390 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -48,7 +48,6 @@ namespace osu.Game.Overlays.Settings subheader.Colour = colourProvider.Content2; subheader.Font = OsuFont.GetFont(size: 18); }); - }) }; } From 0f129b2cfbdfca268cb6c46272682ec4c2f9d7c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Nov 2021 12:45:00 +0900 Subject: [PATCH 047/169] 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 048/169] 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 049/169] 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 050/169] 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(); From eba3cfc96e0e812a764cf70103905f4d7aff0874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 12:37:12 +0100 Subject: [PATCH 051/169] Move `ScoreInfo` string representation to extension method --- osu.Game/Scoring/ScoreInfo.cs | 2 +- osu.Game/Scoring/ScoreInfoExtensions.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Scoring/ScoreInfoExtensions.cs diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index e5b050fc01..736a939a59 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -225,7 +225,7 @@ namespace osu.Game.Scoring return clone; } - public override string ToString() => $"{User} playing {BeatmapInfo}"; + public override string ToString() => this.GetDisplayTitle(); public bool Equals(ScoreInfo other) { diff --git a/osu.Game/Scoring/ScoreInfoExtensions.cs b/osu.Game/Scoring/ScoreInfoExtensions.cs new file mode 100644 index 0000000000..2279337fef --- /dev/null +++ b/osu.Game/Scoring/ScoreInfoExtensions.cs @@ -0,0 +1,15 @@ +// 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; + +namespace osu.Game.Scoring +{ + public static class ScoreInfoExtensions + { + /// + /// A user-presentable display title representing this score. + /// + public static string GetDisplayTitle(this IScoreInfo scoreInfo) => $"{scoreInfo.User.Username} playing {scoreInfo.Beatmap.GetDisplayTitle()}"; + } +} From a1b55d6490cd103b9d3eb0238305df7a38234a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 13:34:36 +0100 Subject: [PATCH 052/169] Add failing test case --- .../Online/TestSceneBeatmapManager.cs | 52 ++++++++++++++++--- .../Notifications/ProgressNotification.cs | 9 +++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs index 0ae0186770..4d5bee13f2 100644 --- a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs +++ b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Tests.Visual; @@ -16,7 +17,30 @@ namespace osu.Game.Tests.Online private BeatmapManager beatmaps; private ProgressNotification recentNotification; - private static readonly BeatmapSetInfo test_model = new BeatmapSetInfo { OnlineBeatmapSetID = 1 }; + private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo + { + OnlineBeatmapSetID = 1, + Metadata = new BeatmapMetadata + { + Artist = "test author", + Title = "test title", + Author = new APIUser + { + Username = "mapper" + } + } + }; + + private static readonly APIBeatmapSet test_online_model = new APIBeatmapSet + { + OnlineID = 2, + Artist = "test author", + Title = "test title", + Author = new APIUser + { + Username = "mapper" + } + }; [BackgroundDependencyLoader] private void load(BeatmapManager beatmaps) @@ -26,25 +50,41 @@ namespace osu.Game.Tests.Online beatmaps.PostNotification = n => recentNotification = n as ProgressNotification; } + private static readonly object[][] notification_test_cases = + { + new object[] { test_db_model }, + new object[] { test_online_model } + }; + + [TestCaseSource(nameof(notification_test_cases))] + public void TestNotificationMessage(IBeatmapSetInfo model) + { + AddStep("clear recent notification", () => recentNotification = null); + AddStep("download beatmap", () => beatmaps.Download(model)); + + AddUntilStep("wait for notification", () => recentNotification != null); + AddUntilStep("notification text correct", () => recentNotification.Text.ToString() == "Downloading test author - test title (mapper)"); + } + [Test] public void TestCancelDownloadFromRequest() { - AddStep("download beatmap", () => beatmaps.Download(test_model)); + AddStep("download beatmap", () => beatmaps.Download(test_db_model)); - AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_model).Cancel()); + AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_db_model).Cancel()); - AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_model) == null); + AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_db_model) == null); AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled); } [Test] public void TestCancelDownloadFromNotification() { - AddStep("download beatmap", () => beatmaps.Download(test_model)); + AddStep("download beatmap", () => beatmaps.Download(test_db_model)); AddStep("cancel download from notification", () => recentNotification.Close()); - AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_model) == null); + AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_db_model) == null); AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled); } } diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index c44b88ad29..5b74bff817 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -23,9 +23,16 @@ namespace osu.Game.Overlays.Notifications { private const float loading_spinner_size = 22; + private LocalisableString text; + public LocalisableString Text { - set => Schedule(() => textDrawable.Text = value); + get => text; + set + { + text = value; + Schedule(() => textDrawable.Text = text); + } } public string CompletionText { get; set; } = "Task has completed!"; From 5ec8288508876dff6b6af1717b4a9fb6866792f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 13:34:56 +0100 Subject: [PATCH 053/169] Add `GetDisplayString()` extension to handle all model interface types globally --- osu.Game/Database/ArchiveModelManager.cs | 3 +- osu.Game/Database/ModelDownloader.cs | 3 +- osu.Game/Extensions/ModelExtensions.cs | 61 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Extensions/ModelExtensions.cs diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 019749760f..320f108886 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -15,6 +15,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Game.Extensions; using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.IPC; @@ -192,7 +193,7 @@ namespace osu.Game.Database else { notification.CompletionText = imported.Count == 1 - ? $"Imported {imported.First().Value}!" + ? $"Imported {imported.First().Value.GetDisplayString()}!" : $"Imported {imported.Count} {HumanisedModelName}s!"; if (imported.Count > 0 && PostImport != null) diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 3c1f181f24..43ba62dfe0 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Humanizer; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Overlays.Notifications; @@ -50,7 +51,7 @@ namespace osu.Game.Database DownloadNotification notification = new DownloadNotification { - Text = $"Downloading {request.Model}", + Text = $"Downloading {request.Model.GetDisplayString()}", }; request.DownloadProgressed += progress => diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs new file mode 100644 index 0000000000..5c96add076 --- /dev/null +++ b/osu.Game/Extensions/ModelExtensions.cs @@ -0,0 +1,61 @@ +// 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; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Extensions +{ + public static class ModelExtensions + { + /// + /// Returns a user-facing string representing the . + /// + /// + /// + /// Non-interface types without special handling will fall back to . + /// + /// + /// Warning: This method is _purposefully_ not called GetDisplayTitle() like the others, because otherwise + /// extension method type inference rules cause this method to call itself and cause a stack overflow. + /// + /// + public static string GetDisplayString(this object model) + { + string result = null; + + switch (model) + { + case IBeatmapSetInfo beatmapSetInfo: + result = beatmapSetInfo.Metadata?.GetDisplayTitle(); + break; + + case IBeatmapInfo beatmapInfo: + result = beatmapInfo.GetDisplayTitle(); + break; + + case IBeatmapMetadataInfo metadataInfo: + result = metadataInfo.GetDisplayTitle(); + break; + + case IScoreInfo scoreInfo: + result = scoreInfo.GetDisplayTitle(); + break; + + case IRulesetInfo rulesetInfo: + result = rulesetInfo.Name; + break; + + case IUser user: + result = user.Username; + break; + } + + // fallback in case none of the above happens to match. + result ??= model.ToString(); + return result; + } + } +} From 9686bf507d7b49419e802f44d841579a954536b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 13:48:05 +0100 Subject: [PATCH 054/169] Add failing tests for coverage of `GetDisplayString()` --- osu.Game.Tests/Models/DisplayStringTest.cs | 95 ++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 osu.Game.Tests/Models/DisplayStringTest.cs diff --git a/osu.Game.Tests/Models/DisplayStringTest.cs b/osu.Game.Tests/Models/DisplayStringTest.cs new file mode 100644 index 0000000000..9a3c32bfb2 --- /dev/null +++ b/osu.Game.Tests/Models/DisplayStringTest.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Extensions; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Tests.Models +{ + [TestFixture] + public class DisplayStringTest + { + private static readonly object[][] test_cases = + { + new object[] { makeMockBeatmapSet(), "artist - title (author)" }, + new object[] { makeMockBeatmap(), "artist - title (author) [difficulty]" }, + new object[] { makeMockMetadata(), "artist - title (author)" }, + new object[] { makeMockScore(), "user playing artist - title (author) [difficulty]" }, + new object[] { makeMockRuleset(), "ruleset" }, + new object[] { makeMockUser(), "user" }, + new object[] { new Fallback(), "fallback" } + }; + + [TestCaseSource(nameof(test_cases))] + public void TestDisplayString(object model, string expected) => Assert.That(model.GetDisplayString(), Is.EqualTo(expected)); + + private static IBeatmapSetInfo makeMockBeatmapSet() + { + var mock = new Mock(); + + mock.Setup(m => m.Metadata).Returns(makeMockMetadata); + + return mock.Object; + } + + private static IBeatmapInfo makeMockBeatmap() + { + var mock = new Mock(); + + mock.Setup(m => m.Metadata).Returns(makeMockMetadata); + mock.Setup(m => m.DifficultyName).Returns("difficulty"); + + return mock.Object; + } + + private static IBeatmapMetadataInfo makeMockMetadata() + { + var mock = new Mock(); + + mock.Setup(m => m.Artist).Returns("artist"); + mock.Setup(m => m.Title).Returns("title"); + mock.Setup(m => m.Author.Username).Returns("author"); + + return mock.Object; + } + + private static IScoreInfo makeMockScore() + { + var mock = new Mock(); + + mock.Setup(m => m.User).Returns(new APIUser { Username = "user" }); // TODO: temporary. + mock.Setup(m => m.Beatmap).Returns(makeMockBeatmap); + + return mock.Object; + } + + private static IRulesetInfo makeMockRuleset() + { + var mock = new Mock(); + + mock.Setup(m => m.Name).Returns("ruleset"); + + return mock.Object; + } + + private static IUser makeMockUser() + { + var mock = new Mock(); + + mock.Setup(m => m.Username).Returns("user"); + + return mock.Object; + } + + private class Fallback + { + public override string ToString() => "fallback"; + } + } +} From 3d148aea4040615fa8250a2de890b6e318783633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 13:51:18 +0100 Subject: [PATCH 055/169] Fix `GetDisplayTitle()` implementations relying on `ToString()` themselves --- osu.Game/Beatmaps/BeatmapInfoExtensions.cs | 2 +- osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs index 707b588063..eab66b9857 100644 --- a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps /// /// A user-presentable display title representing this beatmap. /// - public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{beatmapInfo.Metadata} {getVersionString(beatmapInfo)}".Trim(); + public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{beatmapInfo.Metadata.GetDisplayTitle()} {getVersionString(beatmapInfo)}".Trim(); /// /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields. diff --git a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs index 27cd7f8d9a..32fb389e9a 100644 --- a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs @@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps /// public static string GetDisplayTitle(this IBeatmapMetadataInfo metadataInfo) { - string author = string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $"({metadataInfo.Author})"; + string author = string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $"({metadataInfo.Author.Username})"; return $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim(); } From 03a315b9f54595446861e5cd6377efd44f17ffb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Nov 2021 14:33:06 +0100 Subject: [PATCH 056/169] Fix missing beatmap in replay download test scene Was causing nullrefs in `GetDisplayTitle()`. --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index d4036fefc0..f47fae33ca 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Gameplay Id = 39828, Username = @"WubWoofWolf", } - }.CreateScoreInfo(rulesets); + }.CreateScoreInfo(rulesets, CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo); } private class TestReplayDownloadButton : ReplayDownloadButton From 64bc8da14c5b2e22d4ddc36e5644a64359133f5f Mon Sep 17 00:00:00 2001 From: Semyon Rozhkov Date: Tue, 9 Nov 2021 17:11:19 +0300 Subject: [PATCH 057/169] Add "No Scope" mod implementation for Catch --- .../Mods/TestSceneCatchModNoScope.cs | 156 ++++++++++++++++++ osu.Game.Rulesets.Catch/CatchRuleset.cs | 1 + .../Mods/CatchModNoScope.cs | 38 +++++ osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 72 +------- osu.Game/Rulesets/Mods/ModNoScope.cs | 75 +++++++++ 5 files changed, 275 insertions(+), 67 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs create mode 100644 osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs create mode 100644 osu.Game/Rulesets/Mods/ModNoScope.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs new file mode 100644 index 0000000000..5d8bbad384 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -0,0 +1,156 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Mods +{ + public class TestSceneCatchModNoScope : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + + [Test] + public void TestVisibleDuringBreak() + { + CreateModTest(new ModTestData + { + Mod = new CatchModNoScope + { + HiddenComboCount = { Value = 0 }, + }, + Autoplay = true, + PassCondition = () => true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 1000, + }, + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 5000, + } + }, + Breaks = new List + { + new BreakPeriod(2000, 4000), + } + } + }); + + AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); + AddUntilStep("wait for start of break", isBreak); + AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); + AddUntilStep("wait for end of break", () => !isBreak()); + AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); + } + + [Test] + public void TestVisibleDuringBananaShower() + { + CreateModTest(new ModTestData + { + Mod = new CatchModNoScope + { + HiddenComboCount = { Value = 0 }, + }, + Autoplay = true, + PassCondition = () => true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 1000, + }, + new BananaShower + { + StartTime = 2000, + Duration = 2000, + }, + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 5000, + } + } + } + }); + + AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); + AddUntilStep("wait for start of banana shower", isBananaShower); + AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); + AddUntilStep("wait for end of banana shower", () => !isBananaShower()); + AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); + } + + [Test] + public void TestVisibleAfterComboBreak() + { + CreateModTest(new ModTestData + { + Mod = new CatchModNoScope + { + HiddenComboCount = { Value = 2 }, + }, + Autoplay = true, + PassCondition = () => true, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = 0, + StartTime = 1000, + }, + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 3000, + }, + new Fruit + { + X = CatchPlayfield.WIDTH, + StartTime = 5000, + }, + } + } + }); + + AddAssert("catcher must start visible", () => catcherAlphaAlmostEquals(1)); + AddUntilStep("wait for combo", () => Player.ScoreProcessor.Combo.Value >= 2); + AddAssert("catcher must dim after combo", () => !catcherAlphaAlmostEquals(1)); + AddStep("break combo", () => Player.ScoreProcessor.Combo.Set(0)); + AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); + } + + private bool isBananaShower() => Player.ChildrenOfType().SingleOrDefault() != null; + + private bool isBreak() => Player.IsBreakTime.Value; + + private bool catcherAlphaAlmostEquals(float alpha) + { + var playfield = (CatchPlayfield)Player.DrawableRuleset.Playfield; + return Precision.AlmostEquals(playfield.CatcherArea.Alpha, alpha); + } + } +} diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9fee6b2bc1..c256172177 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -133,6 +133,7 @@ namespace osu.Game.Rulesets.Catch new MultiMod(new ModWindUp(), new ModWindDown()), new CatchModFloatingFruits(), new CatchModMuted(), + new CatchModNoScope(), }; default: diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs new file mode 100644 index 0000000000..7b55150582 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -0,0 +1,38 @@ +// 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.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Framework.Utils; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Utils; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModNoScope : ModNoScope, IApplicableToBeatmap, IApplicableToDrawableRuleset + { + public override string Description => "Where's the catcher?"; + + public PeriodTracker BananaShowerPeriods; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + BananaShowerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); + } + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + var playfield = (CatchPlayfield)drawableRuleset.Playfield; + playfield.OnUpdate += _ => + { + bool shouldAlwaysShowCatcher = IsBreakTime.Value || BananaShowerPeriods.IsInAny(playfield.Clock.CurrentTime); + float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha; + playfield.CatcherArea.Alpha = (float)Interpolation.Lerp(playfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index 501c0a55bd..3f9cd098cd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -3,93 +3,31 @@ using System; using System.Linq; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Screens.Play; using osu.Game.Utils; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor, IApplicableToPlayer, IApplicableToBeatmap + public class OsuModNoScope : ModNoScope, IUpdatableByPlayfield, IApplicableToBeatmap { - /// - /// Slightly higher than the cutoff for . - /// - private const float min_alpha = 0.0002f; - - private const float transition_duration = 100; - - public override string Name => "No Scope"; - public override string Acronym => "NS"; - public override ModType Type => ModType.Fun; - public override IconUsage? Icon => FontAwesome.Solid.EyeSlash; public override string Description => "Where's the cursor?"; - public override double ScoreMultiplier => 1; - private BindableNumber currentCombo; - private IBindable isBreakTime; private PeriodTracker spinnerPeriods; - private float comboBasedAlpha; - - [SettingSource( - "Hidden at combo", - "The combo count at which the cursor becomes completely hidden", - SettingControlType = typeof(SettingsSlider) - )] - public BindableInt HiddenComboCount { get; } = new BindableInt - { - Default = 10, - Value = 10, - MinValue = 0, - MaxValue = 50, - }; - - public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; - - public void ApplyToPlayer(Player player) - { - isBreakTime = player.IsBreakTime.GetBoundCopy(); - } - public void ApplyToBeatmap(IBeatmap beatmap) { - spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - transition_duration, b.EndTime))); - } - - public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) - { - if (HiddenComboCount.Value == 0) return; - - currentCombo = scoreProcessor.Combo.GetBoundCopy(); - currentCombo.BindValueChanged(combo => - { - comboBasedAlpha = Math.Max(min_alpha, 1 - (float)combo.NewValue / HiddenComboCount.Value); - }, true); + spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); } public virtual void Update(Playfield playfield) { - bool shouldAlwaysShowCursor = isBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime); - float targetAlpha = shouldAlwaysShowCursor ? 1 : comboBasedAlpha; - playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / transition_duration, 0, 1)); + bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime); + float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha; + playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } } - - public class HiddenComboSlider : OsuSliderBar - { - public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText; - } } diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs new file mode 100644 index 0000000000..9e2734b775 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -0,0 +1,75 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModNoScope : Mod, IApplicableToScoreProcessor, IApplicableToPlayer + { + public override string Name => "No Scope"; + public override string Acronym => "NS"; + public override ModType Type => ModType.Fun; + public override IconUsage? Icon => FontAwesome.Solid.EyeSlash; + public override double ScoreMultiplier => 1; + + /// + /// Slightly higher than the cutoff for . + /// + protected const float MIN_ALPHA = 0.0002f; + + protected const float TRANSITION_DURATION = 100; + + protected BindableNumber CurrentCombo; + + protected IBindable IsBreakTime; + + protected float ComboBasedAlpha; + + [SettingSource( + "Hidden at combo", + "The combo count at which the cursor becomes completely hidden", + SettingControlType = typeof(SettingsSlider) + )] + public BindableInt HiddenComboCount { get; } = new BindableInt + { + Default = 10, + Value = 10, + MinValue = 0, + MaxValue = 50, + }; + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + + public void ApplyToPlayer(Player player) + { + IsBreakTime = player.IsBreakTime.GetBoundCopy(); + } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + if (HiddenComboCount.Value == 0) return; + + CurrentCombo = scoreProcessor.Combo.GetBoundCopy(); + CurrentCombo.BindValueChanged(combo => + { + ComboBasedAlpha = Math.Max(MIN_ALPHA, 1 - (float)combo.NewValue / HiddenComboCount.Value); + }, true); + } + } + + public class HiddenComboSlider : OsuSliderBar + { + public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText; + } +} From 286754f6f73c4f0fbf87d0250ec964a088aa41bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Nov 2021 14:24:39 +0100 Subject: [PATCH 058/169] Move clipboard operation implementation down to current screen --- .../Screens/Edit/Compose/ComposeScreen.cs | 55 ++++++++++++++++++- osu.Game/Screens/Edit/Editor.cs | 51 +++-------------- osu.Game/Screens/Edit/EditorScreen.cs | 25 +++++++++ 3 files changed, 86 insertions(+), 45 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 926a2ad4e0..2214c517db 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -13,6 +13,7 @@ using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Extensions; +using osu.Game.IO.Serialization; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -21,15 +22,15 @@ namespace osu.Game.Screens.Edit.Compose { public class ComposeScreen : EditorScreenWithTimeline, IKeyBindingHandler { - [Resolved] - private IBindable beatmap { get; set; } - [Resolved] private GameHost host { get; set; } [Resolved] private EditorClock clock { get; set; } + [Resolved(Name = nameof(Editor.Clipboard))] + private Bindable clipboard { get; set; } + private HitObjectComposer composer; public ComposeScreen() @@ -104,5 +105,53 @@ namespace osu.Game.Screens.Edit.Compose } #endregion + + #region Clipboard operations + + public override void Cut() + { + base.Cut(); + + Copy(); + EditorBeatmap.RemoveRange(EditorBeatmap.SelectedHitObjects.ToArray()); + } + + public override void Copy() + { + base.Copy(); + + if (EditorBeatmap.SelectedHitObjects.Count == 0) + return; + + clipboard.Value = new ClipboardContent(EditorBeatmap).Serialize(); + } + + public override void Paste() + { + base.Paste(); + + if (string.IsNullOrEmpty(clipboard.Value)) + return; + + var objects = clipboard.Value.Deserialize().HitObjects; + + Debug.Assert(objects.Any()); + + double timeOffset = clock.CurrentTime - objects.Min(o => o.StartTime); + + foreach (var h in objects) + h.StartTime += timeOffset; + + EditorBeatmap.BeginChange(); + + EditorBeatmap.SelectedHitObjects.Clear(); + + EditorBeatmap.AddRange(objects); + EditorBeatmap.SelectedHitObjects.AddRange(objects); + + EditorBeatmap.EndChange(); + } + + #endregion } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 81b2847443..f51b527117 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework; @@ -24,7 +23,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; -using osu.Game.IO.Serialization; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; @@ -105,6 +103,9 @@ namespace osu.Game.Screens.Edit [Resolved] private MusicController music { get; set; } + [Cached(Name = nameof(Clipboard))] + public readonly Bindable Clipboard = new Bindable(); + public Editor(EditorLoader loader = null) { this.loader = loader; @@ -307,7 +308,7 @@ namespace osu.Game.Screens.Edit copyMenuItem.Action.Disabled = !hasObjects; }, true); - clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); + Clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); menuBar.Mode.ValueChanged += onModeChanged; } @@ -324,7 +325,7 @@ namespace osu.Game.Screens.Edit public void RestoreState([NotNull] EditorState state) => Schedule(() => { clock.Seek(state.Time); - clipboard.Value = state.ClipboardContent; + Clipboard.Value = state.ClipboardContent; }); protected void Save() @@ -561,45 +562,11 @@ namespace osu.Game.Screens.Edit this.Exit(); } - private readonly Bindable clipboard = new Bindable(); + protected void Cut() => currentScreen?.Cut(); - protected void Cut() - { - Copy(); - editorBeatmap.RemoveRange(editorBeatmap.SelectedHitObjects.ToArray()); - } + protected void Copy() => currentScreen?.Copy(); - protected void Copy() - { - if (editorBeatmap.SelectedHitObjects.Count == 0) - return; - - clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); - } - - protected void Paste() - { - if (string.IsNullOrEmpty(clipboard.Value)) - return; - - var objects = clipboard.Value.Deserialize().HitObjects; - - Debug.Assert(objects.Any()); - - double timeOffset = clock.CurrentTime - objects.Min(o => o.StartTime); - - foreach (var h in objects) - h.StartTime += timeOffset; - - editorBeatmap.BeginChange(); - - editorBeatmap.SelectedHitObjects.Clear(); - - editorBeatmap.AddRange(objects); - editorBeatmap.SelectedHitObjects.AddRange(objects); - - editorBeatmap.EndChange(); - } + protected void Paste() => currentScreen?.Paste(); protected void Undo() => changeHandler.RestoreState(-1); @@ -757,7 +724,7 @@ namespace osu.Game.Screens.Edit protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState { Time = clock.CurrentTimeAccurate, - ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? clipboard.Value : string.Empty + ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Value : string.Empty }); private void cancelExit() diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 2810f78835..a406aeb743 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -49,5 +49,30 @@ namespace osu.Game.Screens.Edit this.ScaleTo(0.98f, 200, Easing.OutQuint) .FadeOut(200, Easing.OutQuint); } + + #region Clipboard operations + + /// + /// Performs a "cut to clipboard" operation appropriate for the given screen. + /// + public virtual void Cut() + { + } + + /// + /// Performs a "copy to clipboard" operation appropriate for the given screen. + /// + public virtual void Copy() + { + } + + /// + /// Performs a "paste from clipboard" operation appropriate for the given screen. + /// + public virtual void Paste() + { + } + + #endregion } } From 042b05a2501a29647d2db18df15ae7e9376ac5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Nov 2021 14:54:31 +0100 Subject: [PATCH 059/169] Move clipboard action availability logic down to editor screens --- .../Screens/Edit/Compose/ComposeScreen.cs | 31 +++++++----- osu.Game/Screens/Edit/Editor.cs | 47 +++++++++++++------ osu.Game/Screens/Edit/EditorScreen.cs | 29 +++++++++++- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 2214c517db..b0a85f33d9 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -77,6 +77,13 @@ namespace osu.Game.Screens.Edit.Compose return new EditorSkinProvidingContainer(EditorBeatmap).WithChild(content); } + protected override void LoadComplete() + { + base.LoadComplete(); + EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => updateClipboardActionAvailability()); + clipboard.BindValueChanged(_ => updateClipboardActionAvailability(), true); + } + #region Input Handling public bool OnPressed(KeyBindingPressEvent e) @@ -108,30 +115,24 @@ namespace osu.Game.Screens.Edit.Compose #region Clipboard operations - public override void Cut() + protected override void PerformCut() { - base.Cut(); + base.PerformCut(); Copy(); EditorBeatmap.RemoveRange(EditorBeatmap.SelectedHitObjects.ToArray()); } - public override void Copy() + protected override void PerformCopy() { - base.Copy(); - - if (EditorBeatmap.SelectedHitObjects.Count == 0) - return; + base.PerformCopy(); clipboard.Value = new ClipboardContent(EditorBeatmap).Serialize(); } - public override void Paste() + protected override void PerformPaste() { - base.Paste(); - - if (string.IsNullOrEmpty(clipboard.Value)) - return; + base.PerformPaste(); var objects = clipboard.Value.Deserialize().HitObjects; @@ -152,6 +153,12 @@ namespace osu.Game.Screens.Edit.Compose EditorBeatmap.EndChange(); } + private void updateClipboardActionAvailability() + { + CanCut.Value = CanCopy.Value = EditorBeatmap.SelectedHitObjects.Any(); + CanPaste.Value = !string.IsNullOrEmpty(clipboard.Value); + } + #endregion } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f51b527117..b8a1dda508 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -181,10 +181,6 @@ namespace osu.Game.Screens.Edit OsuMenuItem undoMenuItem; OsuMenuItem redoMenuItem; - EditorMenuItem cutMenuItem; - EditorMenuItem copyMenuItem; - EditorMenuItem pasteMenuItem; - AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -300,19 +296,15 @@ namespace osu.Game.Screens.Edit changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); - editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => - { - bool hasObjects = editorBeatmap.SelectedHitObjects.Count > 0; - - cutMenuItem.Action.Disabled = !hasObjects; - copyMenuItem.Action.Disabled = !hasObjects; - }, true); - - Clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); - menuBar.Mode.ValueChanged += onModeChanged; } + protected override void LoadComplete() + { + base.LoadComplete(); + setUpClipboardActionAvailability(); + } + /// /// If the beatmap's track has changed, this method must be called to keep the editor in a valid state. /// @@ -562,12 +554,38 @@ namespace osu.Game.Screens.Edit this.Exit(); } + #region Clipboard support + + private EditorMenuItem cutMenuItem; + private EditorMenuItem copyMenuItem; + private EditorMenuItem pasteMenuItem; + + private readonly BindableWithCurrent canCut = new BindableWithCurrent(); + private readonly BindableWithCurrent canCopy = new BindableWithCurrent(); + private readonly BindableWithCurrent canPaste = new BindableWithCurrent(); + + private void setUpClipboardActionAvailability() + { + canCut.Current.BindValueChanged(cut => cutMenuItem.Action.Disabled = !cut.NewValue, true); + canCopy.Current.BindValueChanged(copy => copyMenuItem.Action.Disabled = !copy.NewValue, true); + canPaste.Current.BindValueChanged(paste => pasteMenuItem.Action.Disabled = !paste.NewValue, true); + } + + private void rebindClipboardBindables() + { + canCut.Current = currentScreen.CanCut; + canCopy.Current = currentScreen.CanCopy; + canPaste.Current = currentScreen.CanPaste; + } + protected void Cut() => currentScreen?.Cut(); protected void Copy() => currentScreen?.Copy(); protected void Paste() => currentScreen?.Paste(); + #endregion + protected void Undo() => changeHandler.RestoreState(-1); protected void Redo() => changeHandler.RestoreState(1); @@ -644,6 +662,7 @@ namespace osu.Game.Screens.Edit finally { updateSampleDisabledState(); + rebindClipboardBindables(); } } diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index a406aeb743..516d7a23e0 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -52,25 +53,49 @@ namespace osu.Game.Screens.Edit #region Clipboard operations + public BindableBool CanCut { get; } = new BindableBool(); + /// /// Performs a "cut to clipboard" operation appropriate for the given screen. /// - public virtual void Cut() + protected virtual void PerformCut() { } + public void Cut() + { + if (CanCut.Value) + PerformCut(); + } + + public BindableBool CanCopy { get; } = new BindableBool(); + /// /// Performs a "copy to clipboard" operation appropriate for the given screen. /// - public virtual void Copy() + protected virtual void PerformCopy() { } + public virtual void Copy() + { + if (CanCopy.Value) + PerformCopy(); + } + + public BindableBool CanPaste { get; } = new BindableBool(); + /// /// Performs a "paste from clipboard" operation appropriate for the given screen. /// + protected virtual void PerformPaste() + { + } + public virtual void Paste() { + if (CanPaste.Value) + PerformPaste(); } #endregion From 410e9159d1e70a132a1a404a773a370d8ee050c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Nov 2021 15:27:11 +0100 Subject: [PATCH 060/169] Fix test failures due to missing dependencies --- .../Editor/TestSceneManiaComposeScreen.cs | 4 ++++ osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 0f520215a1..60a5051fb5 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -22,6 +23,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Resolved] private SkinManager skins { get; set; } + [Cached(Name = nameof(Screens.Edit.Editor.Clipboard))] + private Bindable clipboard = new Bindable(); + [SetUpSteps] public void SetUpSteps() { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 4813598c9d..8796088ec6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; @@ -26,6 +27,9 @@ namespace osu.Game.Tests.Visual.Editing } }); + [Cached(Name = nameof(Editor.Clipboard))] + private Bindable clipboard = new Bindable(); + [BackgroundDependencyLoader] private void load() { From fbfed167565b3a8646ad6915d9d04f71f7eae42d Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Tue, 9 Nov 2021 23:05:25 +0100 Subject: [PATCH 061/169] Started on implementing a spinner gap check for catch --- .idea/.idea.osu.Desktop/.idea/indexLayout.xml | 5 + osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 + .../Edit/CatchBeatmapVerifier.cs | 24 +++++ .../Edit/Checks/CheckTooShortSpinnerGap.cs | 91 +++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs create mode 100644 osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs diff --git a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml index 7b08163ceb..90e8a163b9 100644 --- a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml +++ b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml @@ -1,5 +1,10 @@ + + + + + diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9fee6b2bc1..6b26a915dd 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -188,5 +188,7 @@ namespace osu.Game.Rulesets.Catch public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); + + public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier(); } } diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs new file mode 100644 index 0000000000..6aa1749ff9 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Catch.Edit.Checks; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public class CatchBeatmapVerifier : IBeatmapVerifier + { + private readonly List checks = new List + { + new CheckTooShortSpinnerGap() + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + return checks.SelectMany(check => check.Run(context)); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs b/osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs new file mode 100644 index 0000000000..7b0b5c74ce --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.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; +using System.Collections.Generic; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Catch.Edit.Checks +{ + public class CheckTooShortSpinnerGap : ICheck + { + private static readonly int[] spinner_start_delta_threshold = { 250, 250, 125, 125, 62, 62 }; + private static readonly int[] spinner_end_delta_threshold = { 250, 250, 250, 125, 125, 125 }; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Too short spinner gap"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateSpinnerStartGap(this), + new IssueTemplateSpinnerEndGap(this) + }; + + public IEnumerable Run(BeatmapVerifierContext context) + { + var hitObjects = context.Beatmap.HitObjects; + int interpretedDifficulty = (int)context.InterpretedDifficulty; + int expectedStartDelta = spinner_start_delta_threshold[interpretedDifficulty]; + int expectedEndDelta = spinner_end_delta_threshold[interpretedDifficulty]; + + for (int i = 0; i < hitObjects.Count - 1; ++i) + { + if (!(hitObjects[i] is BananaShower bananaShower)) + continue; + + if (i != 0 && hitObjects[i - 1] is CatchHitObject previousHitObject && !(previousHitObject is BananaShower)) + { + double spinnerStartDelta = bananaShower.StartTime - previousHitObject.GetEndTime(); + + if (spinnerStartDelta < expectedStartDelta) + { + yield return new IssueTemplateSpinnerStartGap(this) + .Create(spinnerStartDelta, expectedStartDelta, bananaShower, previousHitObject); + } + } + + if (hitObjects[i + 1] is CatchHitObject nextHitObject && !(nextHitObject is BananaShower)) + { + double spinnerEndDelta = nextHitObject.StartTime - bananaShower.EndTime; + + if (spinnerEndDelta < expectedEndDelta) + { + yield return new IssueTemplateSpinnerEndGap(this) + .Create(spinnerEndDelta, expectedEndDelta, bananaShower, nextHitObject); + } + } + } + } + + public abstract class IssueTemplateSpinnerGap : IssueTemplate + { + protected IssueTemplateSpinnerGap(ICheck check, IssueType issueType, string unformattedMessage) + : base(check, issueType, unformattedMessage) + { + } + + public Issue Create(double deltaTime, int expectedDeltaTime, params HitObject[] hitObjects) + { + return new Issue(hitObjects, this, Math.Floor(deltaTime), expectedDeltaTime); + } + } + + public class IssueTemplateSpinnerStartGap : IssueTemplateSpinnerGap + { + public IssueTemplateSpinnerStartGap(ICheck check) + : base(check, IssueType.Problem, "There is only {0} ms apart between the start of the spinner and the last object, there should be {1} ms or more.") + { + } + } + + public class IssueTemplateSpinnerEndGap : IssueTemplateSpinnerGap + { + public IssueTemplateSpinnerEndGap(ICheck check) + : base(check, IssueType.Problem, "There is only {0} ms apart between the end of the spinner and the next object, there should be {1} ms or more.") + { + } + } + } +} From 5d8f35f3c973a941a017642cf09ba3f167ab7bd2 Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Wed, 10 Nov 2021 00:16:29 +0100 Subject: [PATCH 062/169] Code cleanup and added tests for the spinner check --- .../Editor/Checks/TestCheckBananaShowerGap.cs | 118 ++++++++++++++++++ .../Edit/CatchBeatmapVerifier.cs | 2 +- ...tSpinnerGap.cs => CheckBananaShowerGap.cs} | 31 +++-- 3 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/Checks/TestCheckBananaShowerGap.cs rename osu.Game.Rulesets.Catch/Edit/Checks/{CheckTooShortSpinnerGap.cs => CheckBananaShowerGap.cs} (71%) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/Checks/TestCheckBananaShowerGap.cs b/osu.Game.Rulesets.Catch.Tests/Editor/Checks/TestCheckBananaShowerGap.cs new file mode 100644 index 0000000000..055c8429d7 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/Checks/TestCheckBananaShowerGap.cs @@ -0,0 +1,118 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Edit.Checks; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Catch.Tests.Editor.Checks +{ + [TestFixture] + public class TestCheckBananaShowerGap + { + private CheckBananaShowerGap check; + + [SetUp] + public void Setup() + { + check = new CheckBananaShowerGap(); + } + + [Test] + public void TestAllowedSpinnerGaps() + { + assertOk(mockBeatmap(250, 1000, 1250), DifficultyRating.Easy); + assertOk(mockBeatmap(250, 1000, 1250), DifficultyRating.Normal); + assertOk(mockBeatmap(125, 1000, 1250), DifficultyRating.Hard); + assertOk(mockBeatmap(125, 1000, 1125), DifficultyRating.Insane); + assertOk(mockBeatmap(62, 1000, 1125), DifficultyRating.Expert); + assertOk(mockBeatmap(62, 1000, 1125), DifficultyRating.ExpertPlus); + } + + [Test] + public void TestDisallowedSpinnerGapStart() + { + assertTooShortSpinnerStart(mockBeatmap(249, 1000, 1250), DifficultyRating.Easy); + assertTooShortSpinnerStart(mockBeatmap(249, 1000, 1250), DifficultyRating.Normal); + assertTooShortSpinnerStart(mockBeatmap(124, 1000, 1250), DifficultyRating.Hard); + assertTooShortSpinnerStart(mockBeatmap(124, 1000, 1250), DifficultyRating.Insane); + assertTooShortSpinnerStart(mockBeatmap(61, 1000, 1250), DifficultyRating.Expert); + assertTooShortSpinnerStart(mockBeatmap(61, 1000, 1250), DifficultyRating.ExpertPlus); + } + + [Test] + public void TestDisallowedSpinnerGapEnd() + { + assertTooShortSpinnerEnd(mockBeatmap(250, 1000, 1249), DifficultyRating.Easy); + assertTooShortSpinnerEnd(mockBeatmap(250, 1000, 1249), DifficultyRating.Normal); + assertTooShortSpinnerEnd(mockBeatmap(125, 1000, 1249), DifficultyRating.Hard); + assertTooShortSpinnerEnd(mockBeatmap(125, 1000, 1124), DifficultyRating.Insane); + assertTooShortSpinnerEnd(mockBeatmap(62, 1000, 1124), DifficultyRating.Expert); + assertTooShortSpinnerEnd(mockBeatmap(62, 1000, 1124), DifficultyRating.ExpertPlus); + } + + [Test] + public void TestConsecutiveSpinners() + { + var spinnerConsecutiveBeatmap = new Beatmap + { + HitObjects = new List + { + new BananaShower { StartTime = 0, EndTime = 100, X = 0 }, + new BananaShower { StartTime = 101, EndTime = 200, X = 0 }, + new BananaShower { StartTime = 201, EndTime = 300, X = 0 } + } + }; + + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Easy); + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Normal); + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Hard); + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Insane); + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Expert); + assertOk(spinnerConsecutiveBeatmap, DifficultyRating.ExpertPlus); + } + + private Beatmap mockBeatmap(double bananaShowerStart, double bananaShowerEnd, double nextFruitStart) + { + return new Beatmap + { + HitObjects = new List + { + new Fruit { StartTime = 0, X = 0 }, + new BananaShower { StartTime = bananaShowerStart, EndTime = bananaShowerEnd, X = 0 }, + new Fruit { StartTime = nextFruitStart, X = 0 } + } + }; + } + + private void assertOk(IBeatmap beatmap, DifficultyRating difficultyRating) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); + Assert.That(check.Run(context), Is.Empty); + } + + private void assertTooShortSpinnerStart(IBeatmap beatmap, DifficultyRating difficultyRating) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.All(issue => issue.Template is CheckBananaShowerGap.IssueTemplateBananaShowerStartGap)); + } + + private void assertTooShortSpinnerEnd(IBeatmap beatmap, DifficultyRating difficultyRating) + { + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating); + var issues = check.Run(context).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.All(issue => issue.Template is CheckBananaShowerGap.IssueTemplateBananaShowerEndGap)); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs index 6aa1749ff9..c7a41a4e22 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Edit { private readonly List checks = new List { - new CheckTooShortSpinnerGap() + new CheckBananaShowerGap() }; public IEnumerable Run(BeatmapVerifierContext context) diff --git a/osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs similarity index 71% rename from osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs rename to osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs index 7b0b5c74ce..909a3110b1 100644 --- a/osu.Game.Rulesets.Catch/Edit/Checks/CheckTooShortSpinnerGap.cs +++ b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs @@ -10,7 +10,10 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Catch.Edit.Checks { - public class CheckTooShortSpinnerGap : ICheck + /// + /// Check the spinner/banana shower gaps specified in the osu!catch difficulty specific ranking criteria. + /// + public class CheckBananaShowerGap : ICheck { private static readonly int[] spinner_start_delta_threshold = { 250, 250, 125, 125, 62, 62 }; private static readonly int[] spinner_end_delta_threshold = { 250, 250, 250, 125, 125, 125 }; @@ -19,8 +22,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks public IEnumerable PossibleTemplates => new IssueTemplate[] { - new IssueTemplateSpinnerStartGap(this), - new IssueTemplateSpinnerEndGap(this) + new IssueTemplateBananaShowerStartGap(this), + new IssueTemplateBananaShowerEndGap(this) }; public IEnumerable Run(BeatmapVerifierContext context) @@ -35,33 +38,35 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks if (!(hitObjects[i] is BananaShower bananaShower)) continue; + // Skip if the previous hitobject is a banana shower, consecutive spinners are allowed if (i != 0 && hitObjects[i - 1] is CatchHitObject previousHitObject && !(previousHitObject is BananaShower)) { double spinnerStartDelta = bananaShower.StartTime - previousHitObject.GetEndTime(); if (spinnerStartDelta < expectedStartDelta) { - yield return new IssueTemplateSpinnerStartGap(this) + yield return new IssueTemplateBananaShowerStartGap(this) .Create(spinnerStartDelta, expectedStartDelta, bananaShower, previousHitObject); } } + // Skip if the next hitobject is a banana shower, consecutive spinners are allowed if (hitObjects[i + 1] is CatchHitObject nextHitObject && !(nextHitObject is BananaShower)) { double spinnerEndDelta = nextHitObject.StartTime - bananaShower.EndTime; if (spinnerEndDelta < expectedEndDelta) { - yield return new IssueTemplateSpinnerEndGap(this) + yield return new IssueTemplateBananaShowerEndGap(this) .Create(spinnerEndDelta, expectedEndDelta, bananaShower, nextHitObject); } } } } - public abstract class IssueTemplateSpinnerGap : IssueTemplate + public abstract class IssueTemplateBananaShowerGap : IssueTemplate { - protected IssueTemplateSpinnerGap(ICheck check, IssueType issueType, string unformattedMessage) + protected IssueTemplateBananaShowerGap(ICheck check, IssueType issueType, string unformattedMessage) : base(check, issueType, unformattedMessage) { } @@ -72,18 +77,18 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks } } - public class IssueTemplateSpinnerStartGap : IssueTemplateSpinnerGap + public class IssueTemplateBananaShowerStartGap : IssueTemplateBananaShowerGap { - public IssueTemplateSpinnerStartGap(ICheck check) - : base(check, IssueType.Problem, "There is only {0} ms apart between the start of the spinner and the last object, there should be {1} ms or more.") + public IssueTemplateBananaShowerStartGap(ICheck check) + : base(check, IssueType.Problem, "There is only {0} ms apart between the start of the spinner and the last object, it should not be less than {1} ms.") { } } - public class IssueTemplateSpinnerEndGap : IssueTemplateSpinnerGap + public class IssueTemplateBananaShowerEndGap : IssueTemplateBananaShowerGap { - public IssueTemplateSpinnerEndGap(ICheck check) - : base(check, IssueType.Problem, "There is only {0} ms apart between the end of the spinner and the next object, there should be {1} ms or more.") + public IssueTemplateBananaShowerEndGap(ICheck check) + : base(check, IssueType.Problem, "There is only {0} ms apart between the end of the spinner and the next object, it should not be less than {1} ms.") { } } From bd5caceeb1f2d95d40339fe08af3720d20749967 Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Wed, 10 Nov 2021 00:23:14 +0100 Subject: [PATCH 063/169] Fixed typo in banana shower gap check message --- osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs index 909a3110b1..d6671cb9db 100644 --- a/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs +++ b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks public class IssueTemplateBananaShowerStartGap : IssueTemplateBananaShowerGap { public IssueTemplateBananaShowerStartGap(ICheck check) - : base(check, IssueType.Problem, "There is only {0} ms apart between the start of the spinner and the last object, it should not be less than {1} ms.") + : base(check, IssueType.Problem, "There is only {0} ms between the start of the spinner and the last object, it should not be less than {1} ms.") { } } @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks public class IssueTemplateBananaShowerEndGap : IssueTemplateBananaShowerGap { public IssueTemplateBananaShowerEndGap(ICheck check) - : base(check, IssueType.Problem, "There is only {0} ms apart between the end of the spinner and the next object, it should not be less than {1} ms.") + : base(check, IssueType.Problem, "There is only {0} ms between the end of the spinner and the next object, it should not be less than {1} ms.") { } } From 4e092fed0f472a0755fe650b4f05ac18e3dae65e Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Wed, 10 Nov 2021 00:35:09 +0100 Subject: [PATCH 064/169] Removed accidentally added Rider legacy UserContentModel --- .idea/.idea.osu.Desktop/.idea/indexLayout.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml index 90e8a163b9..7b08163ceb 100644 --- a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml +++ b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml @@ -1,10 +1,5 @@ - - - - - From ce0eb0b26ff5a3ab152f5e9e48f4f991293b990e Mon Sep 17 00:00:00 2001 From: Semyon Rozhkov Date: Wed, 10 Nov 2021 03:53:30 +0300 Subject: [PATCH 065/169] Using IUpdatableByPlayfield --- osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index 7b55150582..b0e374898e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -13,7 +13,7 @@ using osu.Game.Utils; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModNoScope : ModNoScope, IApplicableToBeatmap, IApplicableToDrawableRuleset + public class CatchModNoScope : ModNoScope, IApplicableToBeatmap, IUpdatableByPlayfield { public override string Description => "Where's the catcher?"; @@ -24,15 +24,12 @@ namespace osu.Game.Rulesets.Catch.Mods BananaShowerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); } - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + public virtual void Update(Playfield playfield) { - var playfield = (CatchPlayfield)drawableRuleset.Playfield; - playfield.OnUpdate += _ => - { - bool shouldAlwaysShowCatcher = IsBreakTime.Value || BananaShowerPeriods.IsInAny(playfield.Clock.CurrentTime); - float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha; - playfield.CatcherArea.Alpha = (float)Interpolation.Lerp(playfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); - }; + var catchPlayfield = (CatchPlayfield)playfield; + bool shouldAlwaysShowCatcher = IsBreakTime.Value || BananaShowerPeriods.IsInAny(catchPlayfield.Clock.CurrentTime); + float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha; + catchPlayfield.CatcherArea.Alpha = (float)Interpolation.Lerp(catchPlayfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(catchPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } } } From 41f4f0ab5e7eb373e8abe6d25c627c651415fe21 Mon Sep 17 00:00:00 2001 From: Semyon Rozhkov Date: Wed, 10 Nov 2021 03:57:22 +0300 Subject: [PATCH 066/169] Different setting slider description in each mod --- osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs | 16 ++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 16 ++++++++++++++++ osu.Game/Rulesets/Mods/ModNoScope.cs | 15 +-------------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index b0e374898e..bec53fc3bd 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -3,9 +3,12 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Framework.Utils; +using osu.Game.Configuration; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.UI; @@ -19,6 +22,19 @@ namespace osu.Game.Rulesets.Catch.Mods public PeriodTracker BananaShowerPeriods; + [SettingSource( + "Hidden at combo", + "The combo count at which the catcher becomes completely hidden", + SettingControlType = typeof(SettingsSlider) + )] + public override BindableInt HiddenComboCount { get; } = new BindableInt + { + Default = 10, + Value = 10, + MinValue = 0, + MaxValue = 50, + }; + public void ApplyToBeatmap(IBeatmap beatmap) { BananaShowerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index 3f9cd098cd..a73ddde110 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -3,8 +3,11 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; @@ -18,6 +21,19 @@ namespace osu.Game.Rulesets.Osu.Mods private PeriodTracker spinnerPeriods; + [SettingSource( + "Hidden at combo", + "The combo count at which the cursor becomes completely hidden", + SettingControlType = typeof(SettingsSlider) + )] + public override BindableInt HiddenComboCount { get; } = new BindableInt + { + Default = 10, + Value = 10, + MinValue = 0, + MaxValue = 50, + }; + public void ApplyToBeatmap(IBeatmap beatmap) { spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index 9e2734b775..7a935eb38f 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -6,9 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -36,18 +34,7 @@ namespace osu.Game.Rulesets.Mods protected float ComboBasedAlpha; - [SettingSource( - "Hidden at combo", - "The combo count at which the cursor becomes completely hidden", - SettingControlType = typeof(SettingsSlider) - )] - public BindableInt HiddenComboCount { get; } = new BindableInt - { - Default = 10, - Value = 10, - MinValue = 0, - MaxValue = 50, - }; + public abstract BindableInt HiddenComboCount { get; } public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; From fe2f143e8ab4de3e7aad820e43fe88c1eaaae23f Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 10 Nov 2021 00:59:28 +0000 Subject: [PATCH 067/169] Nerf slider aim for plays with dropped slider ends --- .../Difficulty/OsuDifficultyAttributes.cs | 1 + .../Difficulty/OsuDifficultyCalculator.cs | 11 ++++++++--- .../Difficulty/OsuPerformanceCalculator.cs | 4 ++++ osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 8 ++++++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index a08fe3b7c5..e0a216c8e0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double AimStrain { get; set; } public double SpeedStrain { get; set; } public double FlashlightRating { get; set; } + public double SliderFactor { get; set; } public double ApproachRate { get; set; } public double OverallDifficulty { get; set; } public double DrainRate { get; set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index b0a764dc4d..7ee5f8622c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -34,8 +34,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new OsuDifficultyAttributes { Mods = mods, Skills = skills }; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; - double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; + double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; + + double sliderFactor = aimRating / aimRatingNoSliders; if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -74,6 +77,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty AimStrain = aimRating, SpeedStrain = speedRating, FlashlightRating = flashlightRating, + SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, @@ -108,7 +112,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new Skill[] { - new Aim(mods), + new Aim(mods, true), + new Aim(mods, false), new Speed(mods, hitWindowGreat), new Flashlight(mods) }; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index a9d9ff6985..36ce6730ac 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -125,6 +125,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } + // Estimate the number of sliderends dropped + double estimateSliderEndsDropped = Math.Min(Attributes.SliderCount, Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo)); + aimValue *= 1 - (((1 - Attributes.SliderFactor) * estimateSliderEndsDropped) / Attributes.SliderCount); + aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 4c40a49396..bb85b1669c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Aim : OsuStrainSkill { - public Aim(Mod[] mods) + public Aim(Mod[] mods, bool withSliders) : base(mods) { + this.withSliders = withSliders; } protected override int HistoryLength => 2; @@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double skillMultiplier => 23.25; private double strainDecayBase => 0.15; + private bool withSliders = true; + private double strainValueOf(DifficultyHitObject current) { if (current.BaseObject is Spinner || Previous.Count <= 1 || Previous[0].BaseObject is Spinner) @@ -135,7 +138,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier); // Add in additional slider velocity bonus. - aimStrain += sliderBonus * slider_multiplier; + if (withSliders) + aimStrain += sliderBonus * slider_multiplier; return aimStrain; } From 577bdade5b6369952a65fd44d8c3ad8663da92cd Mon Sep 17 00:00:00 2001 From: Semyon Rozhkov Date: Wed, 10 Nov 2021 04:10:05 +0300 Subject: [PATCH 068/169] Hide catcher during banana shower --- .../Mods/TestSceneCatchModNoScope.cs | 46 ------------------- .../Mods/CatchModNoScope.cs | 15 +----- 2 files changed, 2 insertions(+), 59 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index 5d8bbad384..13d78145b3 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -2,15 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Utils; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Tests.Visual; @@ -61,47 +58,6 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); } - [Test] - public void TestVisibleDuringBananaShower() - { - CreateModTest(new ModTestData - { - Mod = new CatchModNoScope - { - HiddenComboCount = { Value = 0 }, - }, - Autoplay = true, - PassCondition = () => true, - Beatmap = new Beatmap - { - HitObjects = new List - { - new Fruit - { - X = CatchPlayfield.CENTER_X, - StartTime = 1000, - }, - new BananaShower - { - StartTime = 2000, - Duration = 2000, - }, - new Fruit - { - X = CatchPlayfield.CENTER_X, - StartTime = 5000, - } - } - } - }); - - AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); - AddUntilStep("wait for start of banana shower", isBananaShower); - AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); - AddUntilStep("wait for end of banana shower", () => !isBananaShower()); - AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0)); - } - [Test] public void TestVisibleAfterComboBreak() { @@ -143,8 +99,6 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); } - private bool isBananaShower() => Player.ChildrenOfType().SingleOrDefault() != null; - private bool isBreak() => Player.IsBreakTime.Value; private bool catcherAlphaAlmostEquals(float alpha) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index bec53fc3bd..97c6d46e66 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -2,26 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.UI; -using osu.Game.Utils; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModNoScope : ModNoScope, IApplicableToBeatmap, IUpdatableByPlayfield + public class CatchModNoScope : ModNoScope, IUpdatableByPlayfield { public override string Description => "Where's the catcher?"; - public PeriodTracker BananaShowerPeriods; - [SettingSource( "Hidden at combo", "The combo count at which the catcher becomes completely hidden", @@ -35,15 +29,10 @@ namespace osu.Game.Rulesets.Catch.Mods MaxValue = 50, }; - public void ApplyToBeatmap(IBeatmap beatmap) - { - BananaShowerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); - } - public virtual void Update(Playfield playfield) { var catchPlayfield = (CatchPlayfield)playfield; - bool shouldAlwaysShowCatcher = IsBreakTime.Value || BananaShowerPeriods.IsInAny(catchPlayfield.Clock.CurrentTime); + bool shouldAlwaysShowCatcher = IsBreakTime.Value; float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha; catchPlayfield.CatcherArea.Alpha = (float)Interpolation.Lerp(catchPlayfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(catchPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } From 2d2a6d8a187c2ef6d59d12e85413fb01a0e27026 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 10 Nov 2021 04:00:54 +0000 Subject: [PATCH 069/169] Swap to a harsher formula for slider dropped nerf --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 7ee5f8622c..6908521dff 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; - double sliderFactor = aimRating / aimRatingNoSliders; + double sliderFactor = aimRatingNoSliders / aimRating; if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 36ce6730ac..4a37b8027f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Estimate the number of sliderends dropped double estimateSliderEndsDropped = Math.Min(Attributes.SliderCount, Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo)); - aimValue *= 1 - (((1 - Attributes.SliderFactor) * estimateSliderEndsDropped) / Attributes.SliderCount); + aimValue *= (1 - Attributes.SliderFactor) * Math.Pow(1 - (estimateSliderEndsDropped / Attributes.SliderCount), 5.5) + Attributes.SliderFactor; aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index bb85b1669c..747b0fff8c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; // But if the last object is a slider, then we extend the travel velocity through the slider into the current object. - if (osuLastObj.BaseObject is Slider) + if (osuLastObj.BaseObject is Slider && withSliders) { double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end. @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // As above, do the same for the previous hitobject. double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime; - if (osuLastLastObj.BaseObject is Slider) + if (osuLastLastObj.BaseObject is Slider && withSliders) { double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; From 5dc6a9ed21ac66ae681c5878dc9f66990fc9014e Mon Sep 17 00:00:00 2001 From: Naxesss <30292137+Naxesss@users.noreply.github.com> Date: Wed, 10 Nov 2021 05:04:30 +0100 Subject: [PATCH 070/169] Add background stream closed test --- .../Checks/CheckBackgroundQualityTest.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 2918dde2db..42c1121c74 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -111,6 +111,24 @@ namespace osu.Game.Tests.Editing.Checks Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed); } + [Test] + public void TestStreamClosed() + { + var background = new Texture(1920, 1080); + var stream = new Mock(new byte[1024 * 1024]); + + var mock = new Mock(); + mock.SetupGet(w => w.Beatmap).Returns(beatmap); + mock.SetupGet(w => w.Background).Returns(background); + mock.Setup(w => w.GetStream(It.IsAny())).Returns(stream.Object); + + var context = new BeatmapVerifierContext(beatmap, mock.Object); + + Assert.That(check.Run(context), Is.Empty); + + stream.Verify(x => x.Close(), Times.Once()); + } + private BeatmapVerifierContext getContext(Texture background, [CanBeNull] byte[] fileBytes = null) { return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, fileBytes).Object); From b88818579992ce0751aa51b7f2fa93bf369e49f2 Mon Sep 17 00:00:00 2001 From: Naxesss <30292137+Naxesss@users.noreply.github.com> Date: Wed, 10 Nov 2021 05:06:11 +0100 Subject: [PATCH 071/169] Properly dispose of `Stream` in bg quality check --- .../Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 8fa79e2ee8..7ce2ee802e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.IO; using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit.Checks @@ -48,10 +49,14 @@ namespace osu.Game.Rulesets.Edit.Checks yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); - double filesizeMb = context.WorkingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); - if (filesizeMb > max_filesize_mb) - yield return new IssueTemplateTooUncompressed(this).Create(filesizeMb); + using (Stream stream = context.WorkingBeatmap.GetStream(storagePath)) + { + double filesizeMb = stream.Length / (1024d * 1024d); + + if (filesizeMb > max_filesize_mb) + yield return new IssueTemplateTooUncompressed(this).Create(filesizeMb); + } } public class IssueTemplateTooHighResolution : IssueTemplate From 52c740b3770480eda15fc006497a565c691d2308 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Nov 2021 13:44:24 +0900 Subject: [PATCH 072/169] Add failing test showing team display display failure --- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 4 ++++ .../Multiplayer/Participants/TeamDisplay.cs | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index ad92886bab..1efa8d6810 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -127,9 +127,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead); + AddUntilStep("team displays are not displaying teams", () => multiplayerScreenStack.ChildrenOfType().All(d => d.DisplayedTeam == null)); + AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus)); AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); + + AddUntilStep("team displays are displaying teams", () => multiplayerScreenStack.ChildrenOfType().All(d => d.DisplayedTeam != null)); } private void createRoom(Func room) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index 833fbd6605..f2d09d44e9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -88,7 +88,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants }); } - private int? displayedTeam; + public int? DisplayedTeam { get; private set; } protected override void OnRoomUpdated() { @@ -102,19 +102,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants int? newTeam = (userRoomState as TeamVersusUserState)?.TeamID; - if (newTeam == displayedTeam) + if (newTeam == DisplayedTeam) return; // only play the sample if an already valid team changes to another valid team. // this avoids playing a sound for each user if the match type is changed to/from a team mode. - if (newTeam != null && displayedTeam != null) + if (newTeam != null && DisplayedTeam != null) sampleTeamSwap?.Play(); - displayedTeam = newTeam; + DisplayedTeam = newTeam; - if (displayedTeam != null) + if (DisplayedTeam != null) { - box.FadeColour(getColourForTeam(displayedTeam.Value), duration, Easing.OutQuint); + box.FadeColour(getColourForTeam(DisplayedTeam.Value), duration, Easing.OutQuint); box.ScaleTo(new Vector2(box.Scale.X < 0 ? 1 : -1, 1), duration, Easing.OutQuint); this.ScaleTo(Vector2.One, duration, Easing.OutQuint); From 328c72c3589b2ce21bcbccf2a8b0a8b4760634bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Nov 2021 13:38:02 +0900 Subject: [PATCH 073/169] Fix `TeamDisplay` not showing after changing to team versus --- .../Multiplayer/Participants/TeamDisplay.cs | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index f2d09d44e9..1bf62241f2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -29,52 +29,51 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [Resolved] private OsuColour colours { get; set; } + private OsuClickableContainer clickableContent; + public TeamDisplay(MultiplayerRoomUser user) { this.user = user; RelativeSizeAxes = Axes.Y; - Width = 15; + + AutoSizeAxes = Axes.X; Margin = new MarginPadding { Horizontal = 3 }; - - Alpha = 0; - Scale = new Vector2(0, 1); } [BackgroundDependencyLoader] private void load(AudioManager audio) { - box = new Container + InternalChild = clickableContent = new OsuClickableContainer { - RelativeSizeAxes = Axes.Both, - CornerRadius = 5, - Masking = true, + Width = 15, + Alpha = 0, Scale = new Vector2(0, 1), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = new Box + RelativeSizeAxes = Axes.Y, + Action = changeTeam, + Child = box = new Container { - Colour = Color4.White, RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Masking = true, + Scale = new Vector2(0, 1), Anchor = Anchor.Centre, Origin = Anchor.Centre, + Child = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } } }; if (Client.LocalUser?.Equals(user) == true) { - InternalChild = new OsuClickableContainer - { - RelativeSizeAxes = Axes.Both, - TooltipText = "Change team", - Action = changeTeam, - Child = box - }; - } - else - { - InternalChild = box; + clickableContent.Action = changeTeam; + clickableContent.TooltipText = "Change team"; } sampleTeamSwap = audio.Samples.Get(@"Multiplayer/team-swap"); @@ -117,13 +116,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants box.FadeColour(getColourForTeam(DisplayedTeam.Value), duration, Easing.OutQuint); box.ScaleTo(new Vector2(box.Scale.X < 0 ? 1 : -1, 1), duration, Easing.OutQuint); - this.ScaleTo(Vector2.One, duration, Easing.OutQuint); - this.FadeIn(duration); + clickableContent.ScaleTo(Vector2.One, duration, Easing.OutQuint); + clickableContent.FadeIn(duration); } else { - this.ScaleTo(new Vector2(0, 1), duration, Easing.OutQuint); - this.FadeOut(duration); + clickableContent.ScaleTo(new Vector2(0, 1), duration, Easing.OutQuint); + clickableContent.FadeOut(duration); } } From 72ee2b155641d16a428083302bff6da667d9e6c4 Mon Sep 17 00:00:00 2001 From: Naxesss <30292137+Naxesss@users.noreply.github.com> Date: Wed, 10 Nov 2021 06:18:40 +0100 Subject: [PATCH 074/169] Refactor to avoid duplicate code --- .../Checks/CheckBackgroundQualityTest.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 42c1121c74..05bfae7e63 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Editing.Checks { // While this is a problem, it is out of scope for this check and is caught by a different one. beatmap.Metadata.BackgroundFile = string.Empty; - var context = getContext(null, System.Array.Empty()); + var context = getContext(null, new MemoryStream(System.Array.Empty())); Assert.That(check.Run(context), Is.Empty); } @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestTooUncompressed() { - var context = getContext(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); + var context = getContext(new Texture(1920, 1080), new MemoryStream(new byte[1024 * 1024 * 3])); var issues = check.Run(context).ToList(); @@ -117,31 +117,26 @@ namespace osu.Game.Tests.Editing.Checks var background = new Texture(1920, 1080); var stream = new Mock(new byte[1024 * 1024]); - var mock = new Mock(); - mock.SetupGet(w => w.Beatmap).Returns(beatmap); - mock.SetupGet(w => w.Background).Returns(background); - mock.Setup(w => w.GetStream(It.IsAny())).Returns(stream.Object); - - var context = new BeatmapVerifierContext(beatmap, mock.Object); + var context = getContext(background, stream.Object); Assert.That(check.Run(context), Is.Empty); stream.Verify(x => x.Close(), Times.Once()); } - private BeatmapVerifierContext getContext(Texture background, [CanBeNull] byte[] fileBytes = null) + private BeatmapVerifierContext getContext(Texture background, [CanBeNull] Stream stream = null) { - return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, fileBytes).Object); + return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, stream).Object); } /// - /// Returns the mock of the working beatmap with the given background and filesize. + /// Returns the mock of the working beatmap with the given background and its file stream. /// /// The texture of the background. - /// The bytes that represent the background file. - private Mock getMockWorkingBeatmap(Texture background, [CanBeNull] byte[] fileBytes = null) + /// The stream representing the background file. + private Mock getMockWorkingBeatmap(Texture background, [CanBeNull] Stream stream = null) { - var stream = new MemoryStream(fileBytes ?? new byte[1024 * 1024]); + stream ??= new MemoryStream(new byte[1024 * 1024]); var mock = new Mock(); mock.SetupGet(w => w.Beatmap).Returns(beatmap); From 6dc13ef396e8dce1198200cc2b61fd195977a340 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 10 Nov 2021 17:07:06 +0900 Subject: [PATCH 075/169] Use default hover sample instead of song-ping (they're the same sample anyway) --- osu.Game/Screens/Select/Carousel/CarouselHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 40ca3e0764..ed3aea3445 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Both, }; - sampleHover = audio.Samples.Get("SongSelect/song-ping"); + sampleHover = audio.Samples.Get("UI/default-hover"); } public bool InsetForBorder From 8d0f5b0d8201c0a382816ffcd52d2f97eec29e93 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 10 Nov 2021 17:08:51 +0900 Subject: [PATCH 076/169] Add high-pass filter when song selection is confirmed --- osu.Game/Screens/Play/PlayerLoader.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index d852ac2940..4e5ccde579 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Threading.Tasks; using JetBrains.Annotations; +using ManagedBass.Fx; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -67,6 +68,7 @@ namespace osu.Game.Screens.Play private readonly BindableDouble volumeAdjustment = new BindableDouble(1); private AudioFilter lowPassFilter; + private AudioFilter highPassFilter; protected bool BackgroundBrightnessReduction { @@ -168,7 +170,8 @@ namespace osu.Game.Screens.Play }, idleTracker = new IdleTracker(750), }), - lowPassFilter = new AudioFilter(audio.TrackMixer) + lowPassFilter = new AudioFilter(audio.TrackMixer), + highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass) }; if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) @@ -239,6 +242,7 @@ namespace osu.Game.Screens.Play Beatmap.Value.Track.Stop(); Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); + highPassFilter.CutoffTo(0); } public override bool OnExiting(IScreen next) @@ -352,6 +356,7 @@ namespace osu.Game.Screens.Play content.FadeInFromZero(400); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); + highPassFilter.CutoffTo(300); ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); } @@ -364,6 +369,7 @@ namespace osu.Game.Screens.Play content.ScaleTo(0.7f, content_out_duration * 2, Easing.OutQuint); content.FadeOut(content_out_duration, Easing.OutQuint); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, content_out_duration); + highPassFilter.CutoffTo(0, content_out_duration); } private void pushWhenLoaded() From 5f462b6441c25cc30069ca1f13a02d2aa42de936 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 10 Nov 2021 17:12:49 +0900 Subject: [PATCH 077/169] Move beatmap/difficulty change sample playback to outside of debounce --- osu.Game/Screens/Select/SongSelect.cs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a2dea355ac..4943aac77c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -435,6 +435,7 @@ namespace osu.Game.Screens.Select } // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. + private BeatmapInfo beatmapInfoPrevious; private BeatmapInfo beatmapInfoNoDebounce; private RulesetInfo rulesetNoDebounce; @@ -477,6 +478,19 @@ namespace osu.Game.Screens.Select else selectionChangedDebounce = Scheduler.AddDelayed(run, 200); + if (beatmap != beatmapInfoPrevious) + { + if (beatmap != null && beatmapInfoPrevious != null) + { + if (beatmap.BeatmapSetInfoID == beatmapInfoPrevious.BeatmapSetInfoID) + sampleChangeDifficulty.Play(); + else + sampleChangeBeatmap.Play(); + } + + beatmapInfoPrevious = beatmap; + } + void run() { // clear pending task immediately to track any potential nested debounce operation. @@ -508,18 +522,7 @@ namespace osu.Game.Screens.Select if (!EqualityComparer.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo)) { Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); - - int? lastSetID = Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); - - if (beatmap != null) - { - if (beatmap.BeatmapSetInfoID == lastSetID) - sampleChangeDifficulty.Play(); - else - sampleChangeBeatmap.Play(); - } } if (this.IsCurrentScreen()) From b13d020a499dee4f34becb6dc4e2be9a0fc37a05 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 10 Nov 2021 17:13:47 +0900 Subject: [PATCH 078/169] Don't re-use confim-selection sample for the skip intro button --- osu.Game/Screens/Play/SkipOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index b04fcba0c6..c35548c6b4 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -270,7 +270,7 @@ namespace osu.Game.Screens.Play colourNormal = colours.Yellow; colourHover = colours.YellowDark; - sampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); + sampleConfirm = audio.Samples.Get(@"UI/submit-select"); Children = new Drawable[] { From f4ef8419729f4c80ccd8b1a666c180b2267a73f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Nov 2021 18:27:23 +0900 Subject: [PATCH 079/169] Add fallback for cases where beatmap has no author/title/artist specified --- osu.Game.Tests/Models/DisplayStringTest.cs | 22 +++++++++++++++++++ .../Beatmaps/BeatmapMetadataInfoExtensions.cs | 8 +++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Models/DisplayStringTest.cs b/osu.Game.Tests/Models/DisplayStringTest.cs index 9a3c32bfb2..754a849ac8 100644 --- a/osu.Game.Tests/Models/DisplayStringTest.cs +++ b/osu.Game.Tests/Models/DisplayStringTest.cs @@ -17,6 +17,8 @@ namespace osu.Game.Tests.Models { private static readonly object[][] test_cases = { + new object[] { makeNoMetadataMockBeatmapSet(), "unknown artist - unknown title" }, + new object[] { makeNoAuthorMockBeatmapSet(), "artist - title" }, new object[] { makeMockBeatmapSet(), "artist - title (author)" }, new object[] { makeMockBeatmap(), "artist - title (author) [difficulty]" }, new object[] { makeMockMetadata(), "artist - title (author)" }, @@ -29,6 +31,26 @@ namespace osu.Game.Tests.Models [TestCaseSource(nameof(test_cases))] public void TestDisplayString(object model, string expected) => Assert.That(model.GetDisplayString(), Is.EqualTo(expected)); + private static IBeatmapSetInfo makeNoAuthorMockBeatmapSet() + { + var mock = new Mock(); + + mock.Setup(m => m.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Metadata.Title).Returns("title"); + mock.Setup(m => m.Metadata.Author.Username).Returns(string.Empty); + + return mock.Object; + } + + private static IBeatmapSetInfo makeNoMetadataMockBeatmapSet() + { + var mock = new Mock(); + + mock.Setup(m => m.Metadata).Returns(new BeatmapMetadata()); + + return mock.Object; + } + private static IBeatmapSetInfo makeMockBeatmapSet() { var mock = new Mock(); diff --git a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs index 32fb389e9a..7aab6a7a9b 100644 --- a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs @@ -27,8 +27,12 @@ namespace osu.Game.Beatmaps /// public static string GetDisplayTitle(this IBeatmapMetadataInfo metadataInfo) { - string author = string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $"({metadataInfo.Author.Username})"; - return $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim(); + string author = string.IsNullOrEmpty(metadataInfo.Author.Username) ? string.Empty : $" ({metadataInfo.Author.Username})"; + + string artist = string.IsNullOrEmpty(metadataInfo.Artist) ? "unknown artist" : metadataInfo.Artist; + string title = string.IsNullOrEmpty(metadataInfo.Title) ? "unknown title" : metadataInfo.Title; + + return $"{artist} - {title}{author}".Trim(); } /// From a52d9363f9a3a7dc8fe6e9b6d9e83a97f20985e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 11:09:09 +0100 Subject: [PATCH 080/169] Rewrite tests to be easier to follow --- osu.Game.Tests/Models/DisplayStringTest.cs | 83 ++++++++++++---------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Models/DisplayStringTest.cs b/osu.Game.Tests/Models/DisplayStringTest.cs index 754a849ac8..cac5dd1aaa 100644 --- a/osu.Game.Tests/Models/DisplayStringTest.cs +++ b/osu.Game.Tests/Models/DisplayStringTest.cs @@ -15,23 +15,20 @@ namespace osu.Game.Tests.Models [TestFixture] public class DisplayStringTest { - private static readonly object[][] test_cases = + [Test] + public void TestBeatmapSet() { - new object[] { makeNoMetadataMockBeatmapSet(), "unknown artist - unknown title" }, - new object[] { makeNoAuthorMockBeatmapSet(), "artist - title" }, - new object[] { makeMockBeatmapSet(), "artist - title (author)" }, - new object[] { makeMockBeatmap(), "artist - title (author) [difficulty]" }, - new object[] { makeMockMetadata(), "artist - title (author)" }, - new object[] { makeMockScore(), "user playing artist - title (author) [difficulty]" }, - new object[] { makeMockRuleset(), "ruleset" }, - new object[] { makeMockUser(), "user" }, - new object[] { new Fallback(), "fallback" } - }; + var mock = new Mock(); - [TestCaseSource(nameof(test_cases))] - public void TestDisplayString(object model, string expected) => Assert.That(model.GetDisplayString(), Is.EqualTo(expected)); + mock.Setup(m => m.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Metadata.Title).Returns("title"); + mock.Setup(m => m.Metadata.Author.Username).Returns("author"); - private static IBeatmapSetInfo makeNoAuthorMockBeatmapSet() + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)")); + } + + [Test] + public void TestBeatmapSetWithNoAuthor() { var mock = new Mock(); @@ -39,38 +36,34 @@ namespace osu.Game.Tests.Models mock.Setup(m => m.Metadata.Title).Returns("title"); mock.Setup(m => m.Metadata.Author.Username).Returns(string.Empty); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title")); } - private static IBeatmapSetInfo makeNoMetadataMockBeatmapSet() + [Test] + public void TestBeatmapSetWithNoMetadata() { var mock = new Mock(); mock.Setup(m => m.Metadata).Returns(new BeatmapMetadata()); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("unknown artist - unknown title")); } - private static IBeatmapSetInfo makeMockBeatmapSet() - { - var mock = new Mock(); - - mock.Setup(m => m.Metadata).Returns(makeMockMetadata); - - return mock.Object; - } - - private static IBeatmapInfo makeMockBeatmap() + [Test] + public void TestBeatmap() { var mock = new Mock(); - mock.Setup(m => m.Metadata).Returns(makeMockMetadata); + mock.Setup(m => m.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Metadata.Title).Returns("title"); + mock.Setup(m => m.Metadata.Author.Username).Returns("author"); mock.Setup(m => m.DifficultyName).Returns("difficulty"); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author) [difficulty]")); } - private static IBeatmapMetadataInfo makeMockMetadata() + [Test] + public void TestMetadata() { var mock = new Mock(); @@ -78,35 +71,49 @@ namespace osu.Game.Tests.Models mock.Setup(m => m.Title).Returns("title"); mock.Setup(m => m.Author.Username).Returns("author"); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)")); } - private static IScoreInfo makeMockScore() + [Test] + public void TestScore() { var mock = new Mock(); mock.Setup(m => m.User).Returns(new APIUser { Username = "user" }); // TODO: temporary. - mock.Setup(m => m.Beatmap).Returns(makeMockBeatmap); + mock.Setup(m => m.Beatmap.Metadata.Artist).Returns("artist"); + mock.Setup(m => m.Beatmap.Metadata.Title).Returns("title"); + mock.Setup(m => m.Beatmap.Metadata.Author.Username).Returns("author"); + mock.Setup(m => m.Beatmap.DifficultyName).Returns("difficulty"); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("user playing artist - title (author) [difficulty]")); } - private static IRulesetInfo makeMockRuleset() + [Test] + public void TestRuleset() { var mock = new Mock(); mock.Setup(m => m.Name).Returns("ruleset"); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("ruleset")); } - private static IUser makeMockUser() + [Test] + public void TestUser() { var mock = new Mock(); mock.Setup(m => m.Username).Returns("user"); - return mock.Object; + Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("user")); + } + + [Test] + public void TestFallback() + { + var fallback = new Fallback(); + + Assert.That(fallback.GetDisplayString(), Is.EqualTo("fallback")); } private class Fallback From 6d04823b05e67470b6b62ee6b2eb63997c1c8334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 12:00:36 +0100 Subject: [PATCH 081/169] Remove unnecessary `virtual` specs --- osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index 97c6d46e66..a24a6227fe 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Mods MaxValue = 50, }; - public virtual void Update(Playfield playfield) + public void Update(Playfield playfield) { var catchPlayfield = (CatchPlayfield)playfield; bool shouldAlwaysShowCatcher = IsBreakTime.Value; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index a73ddde110..8e377ea632 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods spinnerPeriods = new PeriodTracker(beatmap.HitObjects.OfType().Select(b => new Period(b.StartTime - TRANSITION_DURATION, b.EndTime))); } - public virtual void Update(Playfield playfield) + public void Update(Playfield playfield) { bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime); float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha; From 5e3ac5ac9f4a004bca348a1f23b17f0fc6e132ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 12:05:14 +0100 Subject: [PATCH 082/169] Reset combo in test in a less weird way --- osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index 13d78145b3..bbe543e73e 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods AddAssert("catcher must start visible", () => catcherAlphaAlmostEquals(1)); AddUntilStep("wait for combo", () => Player.ScoreProcessor.Combo.Value >= 2); AddAssert("catcher must dim after combo", () => !catcherAlphaAlmostEquals(1)); - AddStep("break combo", () => Player.ScoreProcessor.Combo.Set(0)); + AddStep("break combo", () => Player.ScoreProcessor.Combo.Value = 0); AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1)); } From 5e31e890ae8779a880f214c66aa64defacdae8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 12:36:23 +0100 Subject: [PATCH 083/169] Extract class for clipboard contents for DI purposes --- .../Editor/TestSceneManiaComposeScreen.cs | 5 ++--- .../Visual/Editing/TestSceneComposeScreen.cs | 5 ++--- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 7 ++++++- osu.Game/Screens/Edit/Editor.cs | 8 ++++---- osu.Game/Screens/Edit/EditorClipboard.cs | 15 +++++++++++++++ 5 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorClipboard.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 60a5051fb5..24d2a786a0 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -23,8 +22,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Resolved] private SkinManager skins { get; set; } - [Cached(Name = nameof(Screens.Edit.Editor.Clipboard))] - private Bindable clipboard = new Bindable(); + [Cached] + private EditorClipboard clipboard = new EditorClipboard(); [SetUpSteps] public void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 8796088ec6..9b8567e853 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; @@ -27,8 +26,8 @@ namespace osu.Game.Tests.Visual.Editing } }); - [Cached(Name = nameof(Editor.Clipboard))] - private Bindable clipboard = new Bindable(); + [Cached] + private EditorClipboard clipboard = new EditorClipboard(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index b0a85f33d9..d78dce5b60 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -28,7 +28,6 @@ namespace osu.Game.Screens.Edit.Compose [Resolved] private EditorClock clock { get; set; } - [Resolved(Name = nameof(Editor.Clipboard))] private Bindable clipboard { get; set; } private HitObjectComposer composer; @@ -77,6 +76,12 @@ namespace osu.Game.Screens.Edit.Compose return new EditorSkinProvidingContainer(EditorBeatmap).WithChild(content); } + [BackgroundDependencyLoader] + private void load(EditorClipboard clipboard) + { + this.clipboard = clipboard.Content.GetBoundCopy(); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b8a1dda508..de265ad94b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -103,8 +103,8 @@ namespace osu.Game.Screens.Edit [Resolved] private MusicController music { get; set; } - [Cached(Name = nameof(Clipboard))] - public readonly Bindable Clipboard = new Bindable(); + [Cached] + public readonly EditorClipboard Clipboard = new EditorClipboard(); public Editor(EditorLoader loader = null) { @@ -317,7 +317,7 @@ namespace osu.Game.Screens.Edit public void RestoreState([NotNull] EditorState state) => Schedule(() => { clock.Seek(state.Time); - Clipboard.Value = state.ClipboardContent; + Clipboard.Content.Value = state.ClipboardContent; }); protected void Save() @@ -743,7 +743,7 @@ namespace osu.Game.Screens.Edit protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState { Time = clock.CurrentTimeAccurate, - ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Value : string.Empty + ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty }); private void cancelExit() diff --git a/osu.Game/Screens/Edit/EditorClipboard.cs b/osu.Game/Screens/Edit/EditorClipboard.cs new file mode 100644 index 0000000000..f6f0c09e00 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorClipboard.cs @@ -0,0 +1,15 @@ +// 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.Bindables; + +namespace osu.Game.Screens.Edit +{ + /// + /// Wraps the contents of the editor clipboard. + /// + public class EditorClipboard + { + public Bindable Content { get; } = new Bindable(); + } +} From b25ad8dfcb498dea0068de61397108c2074daee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 12:49:04 +0100 Subject: [PATCH 084/169] Copy editor timestamp to OS clipboard even when triggered via menu bar Would only work when triggered via Ctrl+C before, and not work at all for Ctrl+X. --- .../Screens/Edit/Compose/ComposeScreen.cs | 49 ++++++------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index d78dce5b60..3b02d42b41 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -7,9 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -20,7 +17,7 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit.Compose { - public class ComposeScreen : EditorScreenWithTimeline, IKeyBindingHandler + public class ComposeScreen : EditorScreenWithTimeline { [Resolved] private GameHost host { get; set; } @@ -89,35 +86,6 @@ namespace osu.Game.Screens.Edit.Compose clipboard.BindValueChanged(_ => updateClipboardActionAvailability(), true); } - #region Input Handling - - public bool OnPressed(KeyBindingPressEvent e) - { - if (e.Action == PlatformAction.Copy) - host.GetClipboard()?.SetText(formatSelectionAsString()); - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - - private string formatSelectionAsString() - { - if (composer == null) - return string.Empty; - - double displayTime = EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).FirstOrDefault()?.StartTime ?? clock.CurrentTime; - string selectionAsString = composer.ConvertSelectionToString(); - - return !string.IsNullOrEmpty(selectionAsString) - ? $"{displayTime.ToEditorFormattedString()} ({selectionAsString}) - " - : $"{displayTime.ToEditorFormattedString()} - "; - } - - #endregion - #region Clipboard operations protected override void PerformCut() @@ -133,6 +101,8 @@ namespace osu.Game.Screens.Edit.Compose base.PerformCopy(); clipboard.Value = new ClipboardContent(EditorBeatmap).Serialize(); + + host.GetClipboard()?.SetText(formatSelectionAsString()); } protected override void PerformPaste() @@ -164,6 +134,19 @@ namespace osu.Game.Screens.Edit.Compose CanPaste.Value = !string.IsNullOrEmpty(clipboard.Value); } + private string formatSelectionAsString() + { + if (composer == null) + return string.Empty; + + double displayTime = EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).FirstOrDefault()?.StartTime ?? clock.CurrentTime; + string selectionAsString = composer.ConvertSelectionToString(); + + return !string.IsNullOrEmpty(selectionAsString) + ? $"{displayTime.ToEditorFormattedString()} ({selectionAsString}) - " + : $"{displayTime.ToEditorFormattedString()} - "; + } + #endregion } } From 34d235b790bf5686a1074a8217e5b2c1b79bd68a Mon Sep 17 00:00:00 2001 From: Semyon Rozhkov Date: Wed, 10 Nov 2021 15:50:36 +0300 Subject: [PATCH 085/169] Reset combo in different way --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index 0d0fefe0ff..8e226c7ded 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods AddAssert("cursor must start visible", () => cursorAlphaAlmostEquals(1)); AddUntilStep("wait for combo", () => Player.ScoreProcessor.Combo.Value >= 2); AddAssert("cursor must dim after combo", () => !cursorAlphaAlmostEquals(1)); - AddStep("break combo", () => Player.ScoreProcessor.Combo.Set(0)); + AddStep("break combo", () => Player.ScoreProcessor.Combo.Value = 0); AddUntilStep("wait for cursor to show", () => cursorAlphaAlmostEquals(1)); } From 30efc589d1a6b33e83402400e2d3f1f732b989a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 14:03:29 +0100 Subject: [PATCH 086/169] Fix logo sample always playing in main menu when initially logged out --- osu.Game/Screens/Menu/MainMenu.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 221b31f855..3da740b85d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -214,10 +215,16 @@ namespace osu.Game.Screens.Menu } else if (!api.IsLoggedIn) { - logo.Action += displayLogin; + // copy out old action to avoid accidentally capturing logo.Action in closure, causing a self-reference loop. + var previousAction = logo.Action; + + // we want to hook into logo.Action to display the login overlay, but also preserve the return value of the old action. + // therefore pass the old action to displayLogin, so that it can return that value. + // this ensures that the OsuLogo sample does not play when it is not desired. + logo.Action = () => displayLogin(previousAction); } - bool displayLogin() + bool displayLogin(Func originalAction) { if (!loginDisplayed.Value) { @@ -225,7 +232,7 @@ namespace osu.Game.Screens.Menu loginDisplayed.Value = true; } - return true; + return originalAction.Invoke(); } } From d370f64ac3e83ece673948b6d9d59804814da615 Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Wed, 10 Nov 2021 19:58:36 +0100 Subject: [PATCH 087/169] Changed finding the spinner gaps via a dictionary instead of getting the thresholds via an array --- .../Edit/Checks/CheckBananaShowerGap.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs index d6671cb9db..4b2933c0e1 100644 --- a/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs +++ b/osu.Game.Rulesets.Catch/Edit/Checks/CheckBananaShowerGap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks.Components; @@ -15,8 +16,15 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks /// public class CheckBananaShowerGap : ICheck { - private static readonly int[] spinner_start_delta_threshold = { 250, 250, 125, 125, 62, 62 }; - private static readonly int[] spinner_end_delta_threshold = { 250, 250, 250, 125, 125, 125 }; + private static readonly Dictionary spinner_delta_threshold = new Dictionary + { + [DifficultyRating.Easy] = (250, 250), + [DifficultyRating.Normal] = (250, 250), + [DifficultyRating.Hard] = (125, 250), + [DifficultyRating.Insane] = (125, 125), + [DifficultyRating.Expert] = (62, 125), + [DifficultyRating.ExpertPlus] = (62, 125) + }; public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Too short spinner gap"); @@ -29,9 +37,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { var hitObjects = context.Beatmap.HitObjects; - int interpretedDifficulty = (int)context.InterpretedDifficulty; - int expectedStartDelta = spinner_start_delta_threshold[interpretedDifficulty]; - int expectedEndDelta = spinner_end_delta_threshold[interpretedDifficulty]; + (int expectedStartDelta, int expectedEndDelta) = spinner_delta_threshold[context.InterpretedDifficulty]; for (int i = 0; i < hitObjects.Count - 1; ++i) { From 5df694e9126b89dd625acfcd545fa58ef00699a3 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 11 Nov 2021 00:42:06 +0000 Subject: [PATCH 088/169] Estimate number of difficult sliders, and increase assumed slider radius --- .../Difficulty/OsuPerformanceCalculator.cs | 9 +++++++-- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 4a37b8027f..661dd0aed6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -125,9 +125,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } - // Estimate the number of sliderends dropped + // We assume 20% of sliders in a map are difficult since there's no way to tell from pp-side. + double estimateDifficultSliders = Attributes.SliderCount * 0.15; double estimateSliderEndsDropped = Math.Min(Attributes.SliderCount, Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo)); - aimValue *= (1 - Attributes.SliderFactor) * Math.Pow(1 - (estimateSliderEndsDropped / Attributes.SliderCount), 5.5) + Attributes.SliderFactor; + + estimateSliderEndsDropped = Math.Min(estimateSliderEndsDropped, estimateDifficultSliders); + + double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 5.5) + Attributes.SliderFactor; + aimValue *= Math.Max(Attributes.SliderFactor, sliderNerfFactor); aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index cbaad93bed..d073d751d0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. private const int min_delta_time = 25; private const float maximum_slider_radius = normalized_radius * 2.4f; - private const float assumed_slider_radius = normalized_radius * 1.65f; + private const float assumed_slider_radius = normalized_radius * 1.8f; protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; From e33c1f9a41df5ea52373ffc453a05fec8056c135 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Nov 2021 11:00:55 +0900 Subject: [PATCH 089/169] Lower filter cutoff frequency --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 4e5ccde579..734caa43d4 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -356,7 +356,7 @@ namespace osu.Game.Screens.Play content.FadeInFromZero(400); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); - highPassFilter.CutoffTo(300); + highPassFilter.CutoffTo(150); ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); } From 86c6837e5d73a385eef53dd6e73a54e995bde6a1 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 11 Nov 2021 02:49:24 +0000 Subject: [PATCH 090/169] Make slider nerf factor more lenient --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 661dd0aed6..6015a16165 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -125,13 +125,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } - // We assume 20% of sliders in a map are difficult since there's no way to tell from pp-side. + // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = Attributes.SliderCount * 0.15; double estimateSliderEndsDropped = Math.Min(Attributes.SliderCount, Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo)); estimateSliderEndsDropped = Math.Min(estimateSliderEndsDropped, estimateDifficultSliders); - double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 5.5) + Attributes.SliderFactor; + double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; aimValue *= Math.Max(Attributes.SliderFactor, sliderNerfFactor); aimValue *= accuracy; From 707510806286d3429943a6bea807ecf9de6bba3e Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 11 Nov 2021 02:57:34 +0000 Subject: [PATCH 091/169] Clean up clamp logic relating to slider end estimate --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6015a16165..5298c2b479 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -127,9 +127,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = Attributes.SliderCount * 0.15; - double estimateSliderEndsDropped = Math.Min(Attributes.SliderCount, Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo)); - - estimateSliderEndsDropped = Math.Min(estimateSliderEndsDropped, estimateDifficultSliders); + double estimateSliderEndsDropped = Math.Min(estimateDifficultSliders, Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo)); double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; aimValue *= Math.Max(Attributes.SliderFactor, sliderNerfFactor); From 16418ac2ab69c1bb8b3b247a218016e4a08fe666 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 14:02:12 +0900 Subject: [PATCH 092/169] Remove outdated comments --- osu.Game/Online/Multiplayer/MatchRoomState.cs | 1 - osu.Game/Online/Multiplayer/MatchUserState.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MatchRoomState.cs b/osu.Game/Online/Multiplayer/MatchRoomState.cs index edd34fb5a3..30d948f878 100644 --- a/osu.Game/Online/Multiplayer/MatchRoomState.cs +++ b/osu.Game/Online/Multiplayer/MatchRoomState.cs @@ -16,7 +16,6 @@ namespace osu.Game.Online.Multiplayer [Serializable] [MessagePackObject] [Union(0, typeof(TeamVersusRoomState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. - // TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead). public abstract class MatchRoomState { } diff --git a/osu.Game/Online/Multiplayer/MatchUserState.cs b/osu.Game/Online/Multiplayer/MatchUserState.cs index 69245deba0..665b64a8b4 100644 --- a/osu.Game/Online/Multiplayer/MatchUserState.cs +++ b/osu.Game/Online/Multiplayer/MatchUserState.cs @@ -16,7 +16,6 @@ namespace osu.Game.Online.Multiplayer [Serializable] [MessagePackObject] [Union(0, typeof(TeamVersusUserState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. - // TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead). public abstract class MatchUserState { } From 98fa253e1ed82e6135ef97e8afbf6958494c8056 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 14:04:17 +0900 Subject: [PATCH 093/169] Replace usage of `TypeNameHandling.All` with custom type converter --- osu.Game/Online/HubClientConnector.cs | 8 ++- ...gnalRDerivedTypeWorkaroundJsonConverter.cs | 60 +++++++++++++++++++ .../Online/SignalRUnionWorkaroundResolver.cs | 17 +++++- 3 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index e9d6960c71..c79660568c 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -161,9 +162,10 @@ namespace osu.Game.Online builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - // TODO: This should only be required to be `TypeNameHandling.Auto`. - // See usage in osu-server-spectator for further documentation as to why this is required. - options.PayloadSerializerSettings.TypeNameHandling = TypeNameHandling.All; + options.PayloadSerializerSettings.Converters = new List + { + new SignalRDerivedTypeWorkaroundJsonConverter(), + }; }); } diff --git a/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs b/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs new file mode 100644 index 0000000000..55516d2223 --- /dev/null +++ b/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable +using System; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace osu.Game.Online +{ + /// + /// A type of that serializes a subset of types used in multiplayer/spectator communication that + /// derive from a known base type. This is a safe alternative to using or , + /// which are known to have security issues. + /// + public class SignalRDerivedTypeWorkaroundJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) => + SignalRUnionWorkaroundResolver.BASE_TYPES.Contains(objectType) || + SignalRUnionWorkaroundResolver.DERIVED_TYPES.Contains(objectType); + + public override object? ReadJson(JsonReader reader, Type objectType, object? o, JsonSerializer jsonSerializer) + { + if (reader.TokenType == JsonToken.Null) + return null; + + JObject obj = JObject.Load(reader); + + string type = (string)obj[@"$dtype"]!; + + var resolvedType = SignalRUnionWorkaroundResolver.DERIVED_TYPES.Single(t => t.Name == type); + + object? instance = Activator.CreateInstance(resolvedType); + + jsonSerializer.Populate(obj["$value"]!.CreateReader(), instance); + + return instance; + } + + public override void WriteJson(JsonWriter writer, object? o, JsonSerializer serializer) + { + if (o == null) + { + writer.WriteNull(); + return; + } + + writer.WriteStartObject(); + + writer.WritePropertyName(@"$dtype"); + serializer.Serialize(writer, o.GetType().Name); + + writer.WritePropertyName(@"$value"); + writer.WriteRawValue(JsonConvert.SerializeObject(o)); + + writer.WriteEndObject(); + } + } +} diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs index e44da044cc..21413f8285 100644 --- a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs +++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs @@ -20,7 +20,22 @@ namespace osu.Game.Online public static readonly MessagePackSerializerOptions OPTIONS = MessagePackSerializerOptions.Standard.WithResolver(new SignalRUnionWorkaroundResolver()); - private static readonly Dictionary formatter_map = new Dictionary + public static readonly IReadOnlyList BASE_TYPES = new[] + { + typeof(MatchServerEvent), + typeof(MatchUserRequest), + typeof(MatchRoomState), + typeof(MatchUserState), + }; + + public static readonly IReadOnlyList DERIVED_TYPES = new[] + { + typeof(ChangeTeamRequest), + typeof(TeamVersusRoomState), + typeof(TeamVersusUserState), + }; + + private static readonly IReadOnlyDictionary formatter_map = new Dictionary { { typeof(TeamVersusUserState), new TypeRedirectingFormatter() }, { typeof(TeamVersusRoomState), new TypeRedirectingFormatter() }, From ea536dea23c9b6cfe75693b679304ff416aa7688 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 14:45:40 +0900 Subject: [PATCH 094/169] Gracefully handle missing type rather than triggering `ArgumentNullException` --- osu.Game/Rulesets/RulesetStore.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 391bf2c07d..914f33a7ae 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -129,13 +129,18 @@ namespace osu.Game.Rulesets { try { - var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo).AsNonNull())).RulesetInfo; + var resolvedType = Type.GetType(r.InstantiationInfo); - r.Name = instanceInfo.Name; - r.ShortName = instanceInfo.ShortName; - r.InstantiationInfo = instanceInfo.InstantiationInfo; + if (resolvedType != null) + { + var instanceInfo = ((Ruleset)Activator.CreateInstance(resolvedType)).RulesetInfo; - r.Available = true; + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; + + r.Available = true; + } } catch { From 57c333b472f724c24aab22e1890ed0b993926755 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 11 Nov 2021 15:29:08 +0900 Subject: [PATCH 095/169] Remove unused using --- osu.Game/Rulesets/RulesetStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 914f33a7ae..1ddd5396f4 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Reflection; using osu.Framework; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; From f38d6ef8db67229dc7a4f440aa63be23d7a6c882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 08:39:59 +0100 Subject: [PATCH 096/169] Add failing test steps --- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 13 +++++++++++-- .../Visual/Multiplayer/TestMultiplayerClient.cs | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 1efa8d6810..99ff307235 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -10,6 +10,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -97,16 +98,24 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddStep("add another user", () => client.AddUser(new APIUser { Username = "otheruser", Id = 44 })); - AddStep("press button", () => + AddStep("press own button", () => { InputManager.MoveMouseTo(multiplayerScreenStack.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); AddAssert("user on team 1", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1); - AddStep("press button", () => InputManager.Click(MouseButton.Left)); + AddStep("press own button again", () => InputManager.Click(MouseButton.Left)); AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + + AddStep("press other user's button", () => + { + InputManager.MoveMouseTo(multiplayerScreenStack.ChildrenOfType().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + AddAssert("user still on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); } [Test] diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 0860e45346..f81ca70631 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -71,6 +71,21 @@ namespace osu.Game.Tests.Visual.Multiplayer // We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation. Scheduler.Update(); + + switch (Room?.MatchState) + { + case TeamVersusRoomState teamVersus: + Debug.Assert(Room != null); + + // simulate the server's automatic assignment of users to teams on join. + // the "best" team is the one with the least users on it. + int bestTeam = teamVersus.Teams + .Select(team => (teamID: team.ID, userCount: Room.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) + .OrderBy(pair => pair.userCount) + .First().teamID; + ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState { TeamID = bestTeam }).Wait(); + break; + } } public void RemoveUser(APIUser user) From 9664b9a97ca60957c2137e7c08b59c2ed12792ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 08:40:50 +0100 Subject: [PATCH 097/169] Fix being able to switch own team by clicking other players' team indicators --- .../Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index 1bf62241f2..73aca0acdc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -51,7 +51,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Alpha = 0, Scale = new Vector2(0, 1), RelativeSizeAxes = Axes.Y, - Action = changeTeam, Child = box = new Container { RelativeSizeAxes = Axes.Both, From ebe58cee1129a01d7c9c420cbdb94a16751e9f08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 17:18:51 +0900 Subject: [PATCH 098/169] Rename `BeatmapInfo.StarDifficulty` to `StarRating` to match underlying interface --- .../NonVisual/Filtering/FilterMatchingTest.cs | 2 +- .../Visual/Multiplayer/TestSceneDrawableRoom.cs | 8 ++++---- .../Multiplayer/TestSceneStarRatingRangeDisplay.cs | 4 ++-- .../Visual/SongSelect/TestSceneAdvancedStats.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 10 +++++----- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 2 +- .../SongSelect/TestSceneBeatmapMetadataDisplay.cs | 4 ++-- .../SongSelect/TestSceneBeatmapRecommendations.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 8 +++----- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- osu.Game/Beatmaps/DifficultyRecommender.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 6 +++--- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 16 files changed, 30 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 41939cec3f..743d11541d 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.NonVisual.Filtering private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { Ruleset = new RulesetInfo { ID = 5 }, - StarDifficulty = 4.0d, + StarRating = 4.0d, BaseDifficulty = new BeatmapDifficulty { ApproachRate = 5.0f, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 721862c177..2c28a1752e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { BeatmapInfo = { - StarDifficulty = 2.5 + StarRating = 2.5 } }.BeatmapInfo, } @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { BeatmapInfo = { - StarDifficulty = 2.5, + StarRating = 2.5, Metadata = { Artist = "very very very very very very very very very long artist", @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { BeatmapInfo = { - StarDifficulty = 2.5 + StarRating = 2.5 } }.BeatmapInfo, } @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { BeatmapInfo = { - StarDifficulty = 4.5 + StarRating = 4.5 } }.BeatmapInfo, } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs index cdeafdc9a3..20db922122 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -31,8 +31,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value.Playlist.AddRange(new[] { - new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = min } } }, - new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = max } } }, + new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarRating = min } } }, + new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarRating = max } } }, }); }); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 07e68ef509..d57b3dec5d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.SongSelect OverallDifficulty = 5.7f, ApproachRate = 3.5f }, - StarDifficulty = 4.5f + StarRating = 4.5f }; [Test] @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.SongSelect OverallDifficulty = 4.5f, ApproachRate = 3.1f }, - StarDifficulty = 8 + StarRating = 8 }); AddAssert("first bar text is Key Count", () => advancedStats.ChildrenOfType().First().Text == "Key Count"); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 9a142f3ca8..3a5af6811d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -435,8 +435,8 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { var set = createTestBeatmapSet(i); - set.Beatmaps[0].StarDifficulty = 3 - i; - set.Beatmaps[2].StarDifficulty = 6 + i; + set.Beatmaps[0].StarRating = 3 - i; + set.Beatmaps[2].StarRating = 6 + i; sets.Add(set); } @@ -686,7 +686,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Version = $"Stars: {i}", Ruleset = new OsuRuleset().RulesetInfo, - StarDifficulty = i, + StarRating = i, }); } @@ -869,7 +869,7 @@ namespace osu.Game.Tests.Visual.SongSelect { OnlineBeatmapID = id++ * 10, Version = version, - StarDifficulty = diff, + StarRating = diff, Ruleset = new OsuRuleset().RulesetInfo, BaseDifficulty = new BeatmapDifficulty { @@ -904,7 +904,7 @@ namespace osu.Game.Tests.Visual.SongSelect Path = $"extra{b}.osu", Version = $"Extra {b}", Ruleset = rulesets.GetRuleset((b - 1) % 4), - StarDifficulty = 2, + StarRating = 2, BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 3.5f, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 9fa0eab548..666969eb2a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.SongSelect Title = $"{ruleset.ShortName}Title" }, Ruleset = ruleset, - StarDifficulty = 6, + StarRating = 6, Version = $"{ruleset.ShortName}Version", BaseDifficulty = new BeatmapDifficulty() }, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 50ae673c06..574ccce6d5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.SongSelect Title = title, }, Version = version, - StarDifficulty = RNG.NextDouble(0, 10), + StarRating = RNG.NextDouble(0, 10), } })); } @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.SongSelect Title = "Heavy beatmap", }, Version = "10k objects", - StarDifficulty = 99.99f, + StarRating = 99.99f, } })); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 68d5836cac..2c41ea1f53 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.SongSelect Metadata = metadata, BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, - StarDifficulty = difficultyIndex + 1, + StarRating = difficultyIndex + 1, Version = $"SR{difficultyIndex + 1}" }).ToList() }; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 01a819dead..613b0599a0 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -139,7 +139,8 @@ namespace osu.Game.Beatmaps private string versionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; [JsonProperty("difficulty_rating")] - public double StarDifficulty { get; set; } + [Column("StarDifficulty")] + public double StarRating { get; set; } /// /// Currently only populated for beatmap deletion. Use to query scores. @@ -147,7 +148,7 @@ namespace osu.Game.Beatmaps public List Scores { get; set; } [JsonIgnore] - public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarDifficulty); + public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarRating); public override string ToString() => this.GetDisplayTitle(); @@ -197,9 +198,6 @@ namespace osu.Game.Beatmaps [JsonIgnore] IRulesetInfo IBeatmapInfo.Ruleset => Ruleset; - [JsonIgnore] - double IBeatmapInfo.StarRating => StarDifficulty; - #endregion } } diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index a654b05edb..d166f42cde 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -407,7 +407,7 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Ruleset = ruleset; // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; + beatmap.BeatmapInfo.StarRating = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; beatmap.BeatmapInfo.Length = calculateLength(beatmap); beatmap.BeatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength(); diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 638366c580..0c93c4b9db 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -38,7 +38,7 @@ namespace osu.Game.Beatmaps /// /// The maximum star difficulty of all beatmaps in this set. /// - public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0; + public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarRating) ?? 0; /// /// The maximum playable length in milliseconds of all beatmaps in this set. diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 86f5e0dabf..a2bd7c6ce9 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b => { - double difference = b.StarDifficulty - recommendation; + double difference = b.StarRating - recommendation; return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder }).FirstOrDefault(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index de265ad94b..0ca7038580 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -723,7 +723,7 @@ namespace osu.Game.Screens.Edit if (difficultyItems.Count > 0) difficultyItems.Add(new EditorMenuItemSpacer()); - foreach (var beatmap in rulesetBeatmaps.OrderBy(b => b.StarDifficulty)) + foreach (var beatmap in rulesetBeatmaps.OrderBy(b => b.StarRating)) difficultyItems.Add(createDifficultyMenuItem(beatmap)); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index fa96e6dde7..1334784613 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select.Carousel return; } - match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarDifficulty); + match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarRating); match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.BaseDifficulty.ApproachRate); match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.BaseDifficulty.DrainRate); match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.BaseDifficulty.CircleSize); @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) || criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode); - match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarDifficulty); + match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); if (match) { @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Select.Carousel int ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); if (ruleset != 0) return ruleset; - return BeatmapInfo.StarDifficulty.CompareTo(otherBeatmap.BeatmapInfo.StarDifficulty); + return BeatmapInfo.StarRating.CompareTo(otherBeatmap.BeatmapInfo.StarRating); } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index e465f423bc..9e411d5daa 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Select.Carousel return compareUsingAggregateMax(otherSet, b => b.Length); case SortMode.Difficulty: - return compareUsingAggregateMax(otherSet, b => b.StarDifficulty); + return compareUsingAggregateMax(otherSet, b => b.StarRating); } } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 83e1423504..55a3a6874e 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -228,7 +228,7 @@ namespace osu.Game.Tests.Visual Checksum = beatmap.MD5Hash, AuthorID = beatmap.Metadata.Author.OnlineID, RulesetID = beatmap.RulesetID, - StarRating = beatmap.StarDifficulty, + StarRating = beatmap.StarRating, DifficultyName = beatmap.Version, } } From 51a353e12db189f9958228d30fe045b8460c6b92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 17:19:53 +0900 Subject: [PATCH 099/169] Rename `BeatmapInfo.Version` to `DifficultyName` to match underlying interface --- .../Editor/TestSceneEditorSaving.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 6 +++--- osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs | 2 +- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 4 ++-- .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 2 +- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 6 +++--- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapRecommendations.cs | 2 +- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 8 ++------ osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- .../Screens/Edit/Components/Menus/DifficultyMenuItem.cs | 2 +- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 4 ++-- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 2 +- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 26 files changed, 37 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs index 159a64d1ac..42ab84714a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; editorBeatmap.BeatmapInfo.Metadata.Title = "title"; }); - AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.Version = "difficulty"); + AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); checkMutations(); @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor 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"); + AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); } } } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 0bd7c19200..304a65e5c7 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("Soleily", metadata.Artist); Assert.AreEqual("Soleily", metadata.ArtistUnicode); Assert.AreEqual("Gamu", metadata.Author.Username); - Assert.AreEqual("Insane", beatmapInfo.Version); + Assert.AreEqual("Insane", beatmapInfo.DifficultyName); Assert.AreEqual(string.Empty, metadata.Source); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID); diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index a19e977c1a..3093a5719d 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -790,12 +790,12 @@ namespace osu.Game.Tests.Beatmaps.IO // Update via the beatmap, not the beatmap info, to ensure correct linking BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap; - beatmapToUpdate.BeatmapInfo.Version = "updated"; + beatmapToUpdate.BeatmapInfo.DifficultyName = "updated"; manager.Update(setToUpdate); BeatmapInfo updatedInfo = manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID); - Assert.That(updatedInfo.Version, Is.EqualTo("updated")); + Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated")); } finally { @@ -863,7 +863,7 @@ namespace osu.Game.Tests.Beatmaps.IO var beatmap = working.Beatmap; - beatmap.BeatmapInfo.Version = "difficulty"; + beatmap.BeatmapInfo.DifficultyName = "difficulty"; beatmap.BeatmapInfo.Metadata = new BeatmapMetadata { Artist = "Artist/With\\Slashes", diff --git a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs index da7f32a2d3..4a7d7505ad 100644 --- a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs +++ b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps Title = "title", Author = new APIUser { Username = "creator" } }, - Version = "difficulty" + DifficultyName = "difficulty" }; Assert.That(beatmap.ToString(), Is.EqualTo("artist - title (creator) [difficulty]")); diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 743d11541d..ec97948532 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Source = "unit tests", Tags = "look for tags too", }, - Version = "version as well", + DifficultyName = "version as well", Length = 2500, BPM = 160, BeatDivisor = 12, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index af3d9beb69..e1e869cfbf 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editing editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; editorBeatmap.BeatmapInfo.Metadata.Title = "title"; }); - AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.Version = "difficulty"); + AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7); 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"); + AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index ba30315663..5e55759e01 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Ruleset = new OsuRuleset().RulesetInfo, OnlineBeatmapID = beatmapId, - Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", + DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 3a5af6811d..03079fdc5f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -684,7 +684,7 @@ namespace osu.Game.Tests.Visual.SongSelect { set.Beatmaps.Add(new BeatmapInfo { - Version = $"Stars: {i}", + DifficultyName = $"Stars: {i}", Ruleset = new OsuRuleset().RulesetInfo, StarRating = i, }); @@ -868,7 +868,7 @@ namespace osu.Game.Tests.Visual.SongSelect yield return new BeatmapInfo { OnlineBeatmapID = id++ * 10, - Version = version, + DifficultyName = version, StarRating = diff, Ruleset = new OsuRuleset().RulesetInfo, BaseDifficulty = new BeatmapDifficulty @@ -902,7 +902,7 @@ namespace osu.Game.Tests.Visual.SongSelect { OnlineBeatmapID = b * 10, Path = $"extra{b}.osu", - Version = $"Extra {b}", + DifficultyName = $"Extra {b}", Ruleset = rulesets.GetRuleset((b - 1) % 4), StarRating = 2, BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 666969eb2a..1b070c00bf 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -199,7 +199,7 @@ namespace osu.Game.Tests.Visual.SongSelect }, Ruleset = ruleset, StarRating = 6, - Version = $"{ruleset.ShortName}Version", + DifficultyName = $"{ruleset.ShortName}Version", BaseDifficulty = new BeatmapDifficulty() }, HitObjects = objects @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.SongSelect Source = "Verrrrry long Source", Title = "Verrrrry long Title" }, - Version = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version", + DifficultyName = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version", Status = BeatmapSetOnlineStatus.Graveyard, }, }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 574ccce6d5..9473b058cc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Title = title, }, - Version = version, + DifficultyName = version, StarRating = RNG.NextDouble(0, 10), } })); @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Title = "Heavy beatmap", }, - Version = "10k objects", + DifficultyName = "10k objects", StarRating = 99.99f, } })); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 2c41ea1f53..57f2d436c5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.SongSelect BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, StarRating = difficultyIndex + 1, - Version = $"SR{difficultyIndex + 1}" + DifficultyName = $"SR{difficultyIndex + 1}" }).ToList() }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 4861354921..a0d78fff58 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -919,7 +919,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Ruleset = getRuleset(), OnlineBeatmapID = beatmapId, - Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", + DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 07759d598e..7353e47229 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.UserInterface Username = "TestAuthor" }, }, - Version = "Insane" + DifficultyName = "Insane" }, } }, diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 98087994b7..f69a10f000 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -56,7 +56,7 @@ namespace osu.Game.Beatmaps Title = @"Unknown", AuthorString = @"Unknown Creator", }, - Version = @"Normal", + DifficultyName = @"Normal", BaseDifficulty = Difficulty, }; } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 613b0599a0..5bbd48f26d 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -134,9 +134,8 @@ namespace osu.Game.Beatmaps public double TimelineZoom { get; set; } // Metadata - public string Version { get; set; } - - private string versionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; + [Column("Version")] + public string DifficultyName { get; set; } [JsonProperty("difficulty_rating")] [Column("StarDifficulty")] @@ -183,9 +182,6 @@ namespace osu.Game.Beatmaps #region Implementation of IBeatmapInfo - [JsonIgnore] - string IBeatmapInfo.DifficultyName => Version; - [JsonIgnore] IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSet?.Metadata ?? new BeatmapMetadata(); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index d166f42cde..ff4305dc91 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -215,7 +215,7 @@ namespace osu.Game.Beatmaps var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo(); // metadata may have changed; update the path with the standard format. - beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu"); + beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu"); beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index bef2d78f21..f0c19f80f3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -251,7 +251,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Version": - beatmap.BeatmapInfo.Version = pair.Value; + beatmap.BeatmapInfo.DifficultyName = pair.Value; break; case @"Source": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 9117da5d32..bcb14526c7 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -130,7 +130,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}")); if (!string.IsNullOrEmpty(beatmap.Metadata.ArtistUnicode)) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}")); writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.Author.Username}")); - writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}")); + writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.DifficultyName}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Source)) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Tags)) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}")); diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs index c458b65607..75dc479c25 100644 --- a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Components.Menus public BeatmapInfo BeatmapInfo { get; } public DifficultyMenuItem(BeatmapInfo beatmapInfo, bool selected, Action difficultyChangeFunc) - : base(beatmapInfo.Version ?? "(unnamed)", null) + : base(beatmapInfo.DifficultyName ?? "(unnamed)", null) { BeatmapInfo = beatmapInfo; State.Value = selected; diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 34c2fa8480..0d2b093a2e 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit.Setup Empty(), creatorTextBox = createTextBox("Creator", metadata.Author.Username), - difficultyTextBox = createTextBox("Difficulty Name", Beatmap.BeatmapInfo.Version), + difficultyTextBox = createTextBox("Difficulty Name", Beatmap.BeatmapInfo.DifficultyName), sourceTextBox = createTextBox("Source", metadata.Source), tagsTextBox = createTextBox("Tags", metadata.Tags) }; @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Metadata.Title = RomanisedTitleTextBox.Current.Value; Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value; - Beatmap.BeatmapInfo.Version = difficultyTextBox.Current.Value; + Beatmap.BeatmapInfo.DifficultyName = difficultyTextBox.Current.Value; Beatmap.Metadata.Source = sourceTextBox.Current.Value; Beatmap.Metadata.Tags = tagsTextBox.Current.Value; } diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 909f0a2b65..430571e1da 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Play { new OsuSpriteText { - Text = beatmap?.BeatmapInfo?.Version, + Text = beatmap?.BeatmapInfo?.DifficultyName, Font = OsuFont.GetFont(size: 26, italics: true), Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 29b9d6164e..e31a182a49 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Ranking.Expanded { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = beatmap.Version, + Text = beatmap.DifficultyName, Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold), }, new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12)) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index e344da4027..e4cf9bd868 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Select { VersionLabel = new OsuSpriteText { - Text = beatmapInfo.Version, + Text = beatmapInfo.DifficultyName, Font = OsuFont.GetFont(size: 24, italics: true), RelativeSizeAxes = Axes.X, Truncate = true, @@ -324,7 +324,7 @@ namespace osu.Game.Screens.Select }); // no difficulty means it can't have a status to show - if (beatmapInfo.Version == null) + if (beatmapInfo.DifficultyName == null) StatusPill.Hide(); addInfoLabels(); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 5940911d4a..f30bec5d2b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Select.Carousel { new OsuSpriteText { - Text = beatmapInfo.Version, + Text = beatmapInfo.DifficultyName, Font = OsuFont.GetFont(size: 20), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 1ac73cf781..01bf925547 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -253,7 +253,7 @@ namespace osu.Game.Stores var beatmap = new RealmBeatmap(ruleset, difficulty, metadata) { Hash = hash, - DifficultyName = decodedInfo.Version, + DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineBeatmapID ?? -1, AudioLeadIn = decodedInfo.AudioLeadIn, StackLeniency = decodedInfo.StackLeniency, diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 55a3a6874e..a4cef02395 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -229,7 +229,7 @@ namespace osu.Game.Tests.Visual AuthorID = beatmap.Metadata.Author.OnlineID, RulesetID = beatmap.RulesetID, StarRating = beatmap.StarRating, - DifficultyName = beatmap.Version, + DifficultyName = beatmap.DifficultyName, } } }; From 4bca96d548b72725164ffdc73844ac4644bb26af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 17:37:43 +0900 Subject: [PATCH 100/169] Throw again to ensure correct available state is set Also standardises handling between `RulesetStore` and `RealmRulesetStore`. --- osu.Game/Rulesets/RulesetLoadException.cs | 15 +++++++++++++++ osu.Game/Rulesets/RulesetStore.cs | 18 ++++++++---------- osu.Game/Stores/RealmRulesetStore.cs | 18 +++++++----------- 3 files changed, 30 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Rulesets/RulesetLoadException.cs diff --git a/osu.Game/Rulesets/RulesetLoadException.cs b/osu.Game/Rulesets/RulesetLoadException.cs new file mode 100644 index 0000000000..7c3a4bb75d --- /dev/null +++ b/osu.Game/Rulesets/RulesetLoadException.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Rulesets +{ + public class RulesetLoadException : Exception + { + public RulesetLoadException(string message) + : base(@$"Ruleset could not be loaded ({message})") + { + } + } +} diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 1ddd5396f4..6dd036c0e6 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -128,18 +128,16 @@ namespace osu.Game.Rulesets { try { - var resolvedType = Type.GetType(r.InstantiationInfo); + var resolvedType = Type.GetType(r.InstantiationInfo) + ?? throw new RulesetLoadException(@"Type could not be resolved"); - if (resolvedType != null) - { - var instanceInfo = ((Ruleset)Activator.CreateInstance(resolvedType)).RulesetInfo; + var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo + ?? throw new RulesetLoadException(@"Instantiation failure"); - r.Name = instanceInfo.Name; - r.ShortName = instanceInfo.ShortName; - r.InstantiationInfo = instanceInfo.InstantiationInfo; - - r.Available = true; - } + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; + r.Available = true; } catch { diff --git a/osu.Game/Stores/RealmRulesetStore.cs b/osu.Game/Stores/RealmRulesetStore.cs index 0d2cddf874..cf9ffd112c 100644 --- a/osu.Game/Stores/RealmRulesetStore.cs +++ b/osu.Game/Stores/RealmRulesetStore.cs @@ -147,19 +147,15 @@ namespace osu.Game.Stores { try { - var type = Type.GetType(r.InstantiationInfo); + var resolvedType = Type.GetType(r.InstantiationInfo) + ?? throw new RulesetLoadException(@"Type could not be resolved"); - if (type == null) - throw new InvalidOperationException(@"Type resolution failure."); + var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo + ?? throw new RulesetLoadException(@"Instantiation failure"); - var rInstance = (Activator.CreateInstance(type) as Ruleset)?.RulesetInfo; - - if (rInstance == null) - throw new InvalidOperationException(@"Instantiation failure."); - - r.Name = rInstance.Name; - r.ShortName = rInstance.ShortName; - r.InstantiationInfo = rInstance.InstantiationInfo; + r.Name = instanceInfo.Name; + r.ShortName = instanceInfo.ShortName; + r.InstantiationInfo = instanceInfo.InstantiationInfo; r.Available = true; detachedRulesets.Add(r.Clone()); From 53e52d2c4b8f32eb7ce07cb7c2ed8a8cae05c5a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 10:04:16 +0100 Subject: [PATCH 101/169] Fix Android builds failing due to Xamarin-side regression --- .github/workflows/ci.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a3b2fd978..68f8ef51ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,22 +52,29 @@ jobs: build-only-android: name: Build only (Android) - runs-on: windows-latest + runs-on: macos-latest timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v2 + # Pin Xamarin.Android version to 11.2 for now to avoid build failures caused by a Xamarin-side regression. + # See: https://github.com/xamarin/xamarin-android/issues/6284 + # This can be removed/reverted when the fix makes it to upstream and is deployed on github runners. + - name: Set default Xamarin SDK version + run: | + $VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.2 + - name: Install .NET 5.0.x uses: actions/setup-dotnet@v1 with: dotnet-version: "5.0.x" - - name: Setup MSBuild - uses: microsoft/setup-msbuild@v1 - + # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono + # cannot accept .sln(f) files as arguments. + # Build just the main game for now. - name: Build - run: msbuild osu.Android.slnf /restore /p:Configuration=Debug + run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug build-only-ios: # While this workflow technically *can* run, it fails as iOS builds are blocked by multiple issues. From 069ee6980f4297e0bec727ac0949e2a5eac3277f Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Nov 2021 19:20:50 +0900 Subject: [PATCH 102/169] Add debounce to sample playback --- osu.Game/Screens/Select/SongSelect.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4943aac77c..4da15ee53c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -105,6 +105,8 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); + private double audioFeedbackLastPlaybackTime; + [Resolved] private MusicController music { get; set; } @@ -480,12 +482,14 @@ namespace osu.Game.Screens.Select if (beatmap != beatmapInfoPrevious) { - if (beatmap != null && beatmapInfoPrevious != null) + if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) { if (beatmap.BeatmapSetInfoID == beatmapInfoPrevious.BeatmapSetInfoID) sampleChangeDifficulty.Play(); else sampleChangeBeatmap.Play(); + + audioFeedbackLastPlaybackTime = Time.Current; } beatmapInfoPrevious = beatmap; From 9bad912dd0eeafd7219ad902676327a255cf4ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 12:55:14 +0100 Subject: [PATCH 103/169] Add failing test case --- .../Visual/Gameplay/TestSceneUnstableRateCounter.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index 0dc25cf378..be20799479 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -61,6 +61,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("Bring UR to 100", () => applyJudgement(-10, false), 10); } + [Test] + public void TestCounterReceivesJudgementsBeforeCreation() + { + AddRepeatStep("Set UR to 250", () => applyJudgement(25, true), 4); + + AddStep("Create Display", recreateDisplay); + + AddUntilStep("UR = 250", () => counter.Current.Value == 250.0); + } + private void recreateDisplay() { Clear(); From 40cffd1682c25f9037e7f8ff22a1b3337afefcaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 13:09:29 +0100 Subject: [PATCH 104/169] Expose `HitEvents` publically from `ScoreProcessor` --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index c1234f8fb3..fc24972b8e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -53,6 +53,12 @@ namespace osu.Game.Rulesets.Scoring /// public readonly Bindable Mode = new Bindable(); + /// + /// The s collected during gameplay thus far. + /// Intended for use with various statistics displays. + /// + public IReadOnlyList HitEvents => hitEvents; + /// /// The default portion of awarded for hitting s accurately. Defaults to 30%. /// From 69809390d37b4c4e4193562d5d84ac16a05ca590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 13:09:48 +0100 Subject: [PATCH 105/169] Fix HUD unstable rate counter not including judgements before load complete Also unifies UR calculation logic with the results screen statistic item to reduce duplication. --- .../NonVisual/Ranking/UnstableRateTest.cs | 3 +- .../Rulesets/Scoring/HitEventExtensions.cs | 37 +++++++++++++++ .../Screens/Play/HUD/UnstableRateCounter.cs | 46 ++++--------------- .../Ranking/Statistics/UnstableRate.cs | 20 ++------ 4 files changed, 52 insertions(+), 54 deletions(-) create mode 100644 osu.Game/Rulesets/Scoring/HitEventExtensions.cs diff --git a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs index ad6f01881b..103831822c 100644 --- a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs +++ b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs @@ -22,7 +22,8 @@ namespace osu.Game.Tests.NonVisual.Ranking var unstableRate = new UnstableRate(events); - Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value, 10 * Math.Sqrt(10))); + Assert.IsNotNull(unstableRate.Value); + Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value.Value, 10 * Math.Sqrt(10))); } [Test] diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs new file mode 100644 index 0000000000..f645b12483 --- /dev/null +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -0,0 +1,37 @@ +// 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; + +namespace osu.Game.Rulesets.Scoring +{ + public static class HitEventExtensions + { + /// + /// Calculates the "unstable rate" for a sequence of s. + /// + /// + /// A non-null value if unstable rate could be calculated, + /// and if unstable rate cannot be calculated due to being empty. + /// + public static double? CalculateUnstableRate(this IEnumerable hitEvents) + { + double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).ToArray(); + return 10 * standardDeviation(timeOffsets); + } + + private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); + + private static double? standardDeviation(double[] timeOffsets) + { + if (timeOffsets.Length == 0) + return null; + + double mean = timeOffsets.Average(); + double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); + return Math.Sqrt(squares / timeOffsets.Length); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 89ae4f8a21..4dc154fb9d 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -1,9 +1,6 @@ // 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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -29,8 +26,6 @@ namespace osu.Game.Screens.Play.HUD private const float alpha_when_invalid = 0.3f; private readonly Bindable valid = new Bindable(); - private readonly List hitOffsets = new List(); - [Resolved] private ScoreProcessor scoreProcessor { get; set; } @@ -54,41 +49,20 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - scoreProcessor.NewJudgement += onJudgementAdded; - scoreProcessor.JudgementReverted += onJudgementReverted; - } - - private void onJudgementAdded(JudgementResult judgement) - { - if (!changesUnstableRate(judgement)) return; - - hitOffsets.Add(judgement.TimeOffset); + scoreProcessor.NewJudgement += updateDisplay; + scoreProcessor.JudgementReverted += updateDisplay; updateDisplay(); } - private void onJudgementReverted(JudgementResult judgement) - { - if (judgement.FailedAtJudgement || !changesUnstableRate(judgement)) return; - - hitOffsets.RemoveAt(hitOffsets.Count - 1); - updateDisplay(); - } + private void updateDisplay(JudgementResult _) => Scheduler.AddOnce(updateDisplay); 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 = (int)(Math.Sqrt(squares / hitOffsets.Count) * 10); - valid.Value = true; - } - else - { - Current.Value = 0; - valid.Value = false; - } + double? unstableRate = scoreProcessor.HitEvents.CalculateUnstableRate(); + + valid.Value = unstableRate != null; + if (unstableRate != null) + Current.Value = (int)unstableRate.Value; } protected override IHasText CreateText() => new TextComponent @@ -102,8 +76,8 @@ namespace osu.Game.Screens.Play.HUD if (scoreProcessor == null) return; - scoreProcessor.NewJudgement -= onJudgementAdded; - scoreProcessor.JudgementReverted -= onJudgementReverted; + scoreProcessor.NewJudgement -= updateDisplay; + scoreProcessor.JudgementReverted -= updateDisplay; } private class TextComponent : CompositeDrawable, IHasText diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs index cd2b292547..0d23490f40 100644 --- a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -1,9 +1,7 @@ // 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 osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Ranking.Statistics @@ -11,7 +9,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// Displays the unstable rate statistic for a given play. /// - public class UnstableRate : SimpleStatisticItem + public class UnstableRate : SimpleStatisticItem { /// /// Creates and computes an statistic. @@ -20,21 +18,9 @@ namespace osu.Game.Screens.Ranking.Statistics public UnstableRate(IEnumerable hitEvents) : base("Unstable Rate") { - double[] timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()) - .Select(ev => ev.TimeOffset).ToArray(); - Value = 10 * standardDeviation(timeOffsets); + Value = hitEvents.CalculateUnstableRate(); } - private static double standardDeviation(double[] timeOffsets) - { - if (timeOffsets.Length == 0) - return double.NaN; - - double mean = timeOffsets.Average(); - double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); - return Math.Sqrt(squares / timeOffsets.Length); - } - - protected override string DisplayValue(double value) => double.IsNaN(value) ? "(not available)" : value.ToString("N2"); + protected override string DisplayValue(double? value) => value == null ? "(not available)" : value.Value.ToString(@"N2"); } } From 576417947e309712f2ac43713b6b03a111277e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 13:21:56 +0100 Subject: [PATCH 106/169] Round unstable rate in counter rather than truncating --- 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 4dc154fb9d..6cd9ca6154 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -62,7 +63,7 @@ namespace osu.Game.Screens.Play.HUD valid.Value = unstableRate != null; if (unstableRate != null) - Current.Value = (int)unstableRate.Value; + Current.Value = (int)Math.Round(unstableRate.Value); } protected override IHasText CreateText() => new TextComponent From 95bfb2c69b29b00920d7211a8b7cd4218da45c32 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 11 Nov 2021 12:46:22 +0000 Subject: [PATCH 107/169] Clamp slider end estimate to 0 --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 5298c2b479..7c2f70af7d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -127,10 +127,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = Attributes.SliderCount * 0.15; - double estimateSliderEndsDropped = Math.Min(estimateDifficultSliders, Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo)); + double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; - aimValue *= Math.Max(Attributes.SliderFactor, sliderNerfFactor); + aimValue *= sliderNerfFactor; aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 747b0fff8c..2a8d2ce759 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills this.withSliders = withSliders; } + private readonly bool withSliders; + protected override int HistoryLength => 2; private const double wide_angle_multiplier = 1.5; @@ -32,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double skillMultiplier => 23.25; private double strainDecayBase => 0.15; - private bool withSliders = true; - private double strainValueOf(DifficultyHitObject current) { if (current.BaseObject is Spinner || Previous.Count <= 1 || Previous[0].BaseObject is Spinner) From 1ba01a7e9a634b24e06585a4cee7b88b6282a0ee Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 11 Nov 2021 14:37:50 +0000 Subject: [PATCH 108/169] Fix circle-only map NaN values --- .../Difficulty/OsuPerformanceCalculator.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 7c2f70af7d..c28d4e124e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -127,10 +127,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. double estimateDifficultSliders = Attributes.SliderCount * 0.15; - double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); - double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; - aimValue *= sliderNerfFactor; + if (Attributes.SliderCount > 0) + { + double estimateSliderEndsDropped = Math.Min(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), estimateDifficultSliders); + double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; + aimValue *= sliderNerfFactor; + } aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. From c3300934762d2a8b92069098c5da710ede8d5fa3 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 11 Nov 2021 14:42:54 +0000 Subject: [PATCH 109/169] Add clamp back in --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index c28d4e124e..8d45c7a8cc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (Attributes.SliderCount > 0) { - double estimateSliderEndsDropped = Math.Min(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), estimateDifficultSliders); + double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; aimValue *= sliderNerfFactor; } From dbdbfc472307caa185da783894dc5b46981e6be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 16:10:33 +0100 Subject: [PATCH 110/169] Add failing test case for PP counter not initially updating --- .../TestScenePerformancePointsCounter.cs | 67 +++++++++++++------ 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs index 4c48d52acd..84c7f611af 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -1,9 +1,10 @@ // 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.Diagnostics; using NUnit.Framework; -using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets; @@ -20,16 +21,17 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestScenePerformancePointsCounter : OsuTestScene { - [Cached] - private GameplayState gameplayState; + private DependencyProvidingContainer dependencyContainer; - [Cached] + private GameplayState gameplayState; private ScoreProcessor scoreProcessor; private int iteration; + private Bindable lastJudgementResult = new Bindable(); private PerformancePointsCounter counter; - public TestScenePerformancePointsCounter() + [SetUpSteps] + public void SetUpSteps() => AddStep("create components", () => { var ruleset = CreateRuleset(); @@ -38,32 +40,43 @@ namespace osu.Game.Tests.Visual.Gameplay var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo) .GetPlayableBeatmap(ruleset.RulesetInfo); + lastJudgementResult = new Bindable(); + gameplayState = new GameplayState(beatmap, ruleset); + gameplayState.LastJudgementResult.BindTo(lastJudgementResult); + scoreProcessor = new ScoreProcessor(); - } + + Child = dependencyContainer = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(GameplayState), gameplayState), + (typeof(ScoreProcessor), scoreProcessor) + } + }; + + iteration = 0; + }); protected override Ruleset CreateRuleset() => new OsuRuleset(); - [SetUpSteps] - public void SetUpSteps() + private void createCounter() => AddStep("Create counter", () => { - AddStep("Create counter", () => + dependencyContainer.Child = counter = new PerformancePointsCounter { - iteration = 0; - - Child = counter = new PerformancePointsCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5), - }; - }); - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + }; + }); [Test] public void TestBasicCounting() { int previousValue = 0; + createCounter(); AddAssert("counter displaying zero", () => counter.Current.Value == 0); @@ -86,6 +99,17 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("counter non-zero", () => counter.Current.Value > 0); } + [Test] + public void TestCounterUpdatesWithJudgementsBeforeCreation() + { + AddRepeatStep("Add judgement", applyOneJudgement, 10); + + createCounter(); + + AddUntilStep("counter non-zero", () => counter.Current.Value > 0); + AddUntilStep("counter opaque", () => counter.Child.Alpha == 1); + } + private void applyOneJudgement() { var scoreInfo = gameplayState.Score.ScoreInfo; @@ -94,13 +118,14 @@ namespace osu.Game.Tests.Visual.Gameplay scoreInfo.Accuracy = 1; scoreInfo.Statistics[HitResult.Great] = iteration * 1000; - scoreProcessor.ApplyResult(new OsuJudgementResult(new HitObject + lastJudgementResult.Value = new OsuJudgementResult(new HitObject { StartTime = iteration * 10000, }, new OsuJudgement()) { Type = HitResult.Perfect, - }); + }; + scoreProcessor.ApplyResult(lastJudgementResult.Value); iteration++; } From 2e3acffd1de244987d1974fb31f2db4f9fe5b1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 16:23:37 +0100 Subject: [PATCH 111/169] Perform initial update of PP counter value on load complete --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index ef289c2a20..0ce36673ae 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -92,6 +92,9 @@ namespace osu.Game.Screens.Play.HUD scoreProcessor.NewJudgement += onJudgementChanged; scoreProcessor.JudgementReverted += onJudgementChanged; } + + if (gameplayState?.LastJudgementResult.Value != null) + onJudgementChanged(gameplayState.LastJudgementResult.Value); } private bool isValid; From 32b5a736c8b38e34817e5438f9d6ed927d9da27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 14:00:09 +0100 Subject: [PATCH 112/169] Add preview gameplay button to bottom editor bar --- .../Editing/TestSceneEditorTestGameplay.cs | 13 +++++++ .../Timelines/Summary/TestGameplayButton.cs | 34 +++++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 13 ++++++- osu.Game/Screens/Edit/EditorRoundedScreen.cs | 5 +-- osu.Game/Screens/Edit/EditorScreen.cs | 6 ---- 5 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs create mode 100644 osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs new file mode 100644 index 0000000000..a2e210d92f --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -0,0 +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.Game.Rulesets; +using osu.Game.Rulesets.Osu; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorTestGameplay : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs new file mode 100644 index 0000000000..0d7a4ad057 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs @@ -0,0 +1,34 @@ +// 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; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary +{ + public class TestGameplayButton : OsuButton + { + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Font = OsuFont.TorusAlternate.With(weight: FontWeight.Light, size: 24), + Shadow = false + }; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OverlayColourProvider colourProvider) + { + BackgroundColour = colours.Orange1; + SpriteText.Colour = colourProvider.Background6; + + Text = "Test!"; + } + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0ca7038580..ef9cce4bf7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -36,6 +36,7 @@ using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Verify; using osu.Game.Screens.Play; using osu.Game.Users; +using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -106,6 +107,9 @@ namespace osu.Game.Screens.Edit [Cached] public readonly EditorClipboard Clipboard = new EditorClipboard(); + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + public Editor(EditorLoader loader = null) { this.loader = loader; @@ -262,7 +266,8 @@ namespace osu.Game.Screens.Edit { new Dimension(GridSizeMode.Absolute, 220), new Dimension(), - new Dimension(GridSizeMode.Absolute, 220) + new Dimension(GridSizeMode.Absolute, 220), + new Dimension(GridSizeMode.Absolute, 160), }, Content = new[] { @@ -283,6 +288,12 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 10 }, Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, + }, + new TestGameplayButton + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10 }, + Size = new Vector2(1), } }, } diff --git a/osu.Game/Screens/Edit/EditorRoundedScreen.cs b/osu.Game/Screens/Edit/EditorRoundedScreen.cs index 508663224d..7f7b3abc2a 100644 --- a/osu.Game/Screens/Edit/EditorRoundedScreen.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreen.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Edit } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { base.Content.Add(new Container { @@ -41,7 +42,7 @@ namespace osu.Game.Screens.Edit { new Box { - Colour = ColourProvider.Background3, + Colour = colourProvider.Background3, RelativeSizeAxes = Axes.Both, }, roundedContent = new Container diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 516d7a23e0..2837cdcd9a 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -18,9 +17,6 @@ namespace osu.Game.Screens.Edit [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } - [Cached] - protected readonly OverlayColourProvider ColourProvider; - protected override Container Content => content; private readonly Container content; @@ -34,8 +30,6 @@ namespace osu.Game.Screens.Edit Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; - ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - InternalChild = content = new PopoverContainer { RelativeSizeAxes = Axes.Both }; } From b66758dac78c17b2c02ddda536d844acb4a3a319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 18:11:18 +0100 Subject: [PATCH 113/169] Fix missing unsubscribe from `JudgementReverted` --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 0ce36673ae..0db1498756 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -158,7 +158,10 @@ namespace osu.Game.Screens.Play.HUD base.Dispose(isDisposing); if (scoreProcessor != null) + { scoreProcessor.NewJudgement -= onJudgementChanged; + scoreProcessor.JudgementReverted -= onJudgementChanged; + } loadCancellationSource?.Cancel(); } From 59727ce836b6ac76c8dc60a0b3482f3adffdf991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Nov 2021 23:11:25 +0100 Subject: [PATCH 114/169] Add minimal implementation of gameplay testing from editor --- osu.Game/Screens/Edit/Editor.cs | 37 +++++++++++++++++-- osu.Game/Screens/Edit/EditorPlayer.cs | 22 +++++++++++ .../Edit/SaveBeforeGameplayTestDialog.cs | 32 ++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorPlayer.cs create mode 100644 osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ef9cce4bf7..a67ef070f6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -294,6 +294,7 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 10 }, Size = new Vector2(1), + Action = testGameplay } }, } @@ -521,7 +522,21 @@ namespace osu.Game.Screens.Edit ApplyToBackground(b => b.FadeColour(Color4.White, 500)); resetTrack(); - // To update the game-wide beatmap with any changes, perform a re-fetch on exit. + refetchBeatmap(); + + return base.OnExiting(next); + } + + public override void OnSuspending(IScreen next) + { + refetchBeatmap(); + + base.OnSuspending(next); + } + + private void refetchBeatmap() + { + // To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend. // This is required as the editor makes its local changes via EditorBeatmap // (which are not propagated outwards to a potentially cached WorkingBeatmap). var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); @@ -531,8 +546,6 @@ namespace osu.Game.Screens.Edit Logger.Log("Editor providing re-fetched beatmap post edit session"); Beatmap.Value = refetchedBeatmap; } - - return base.OnExiting(next); } private void confirmExitWithSave() @@ -763,6 +776,24 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } + private void testGameplay() + { + if (HasUnsavedChanges) + { + dialogOverlay.Push(new SaveBeforeGameplayTestDialog(() => + { + Save(); + pushEditorPlayer(); + })); + } + else + { + pushEditorPlayer(); + } + + void pushEditorPlayer() => this.Push(new PlayerLoader(() => new EditorPlayer())); + } + public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); diff --git a/osu.Game/Screens/Edit/EditorPlayer.cs b/osu.Game/Screens/Edit/EditorPlayer.cs new file mode 100644 index 0000000000..f8dd75d94a --- /dev/null +++ b/osu.Game/Screens/Edit/EditorPlayer.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.Game.Screens.Play; + +namespace osu.Game.Screens.Edit +{ + public class EditorPlayer : Player + { + public EditorPlayer() + : base(new PlayerConfiguration { ShowResults = false }) + { + } + + protected override void PrepareReplay() + { + // don't record replays. + } + + protected override bool CheckModsAllowFailure() => false; // never fail. + } +} diff --git a/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs b/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs new file mode 100644 index 0000000000..7c03664c66 --- /dev/null +++ b/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs @@ -0,0 +1,32 @@ +// 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 osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Edit +{ + public class SaveBeforeGameplayTestDialog : PopupDialog + { + public SaveBeforeGameplayTestDialog(Action saveAndPreview) + { + HeaderText = "The beatmap will be saved in order to test it."; + + Icon = FontAwesome.Regular.Save; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Sounds good, let's go!", + Action = saveAndPreview + }, + new PopupDialogCancelButton + { + Text = "Oops, continue editing", + }, + }; + } + } +} From 6ce1a78723f3c99c67fc3c4fa9cea40f4ed7775d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 15:08:43 +0100 Subject: [PATCH 115/169] Add test coverage for basic gameplay testing scenarios --- .../Editing/TestSceneEditorTestGameplay.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index a2e210d92f..9c44a6a986 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -1,13 +1,122 @@ // 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.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Tests.Beatmaps.IO; +using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { public class TestSceneEditorTestGameplay : EditorTestScene { + protected override bool IsolateSavingFromDatabase => false; + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + private BeatmapSetInfo importedBeatmapSet; + + public override void SetUpSteps() + { + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).Result); + base.SetUpSteps(); + } + + protected override void LoadEditor() + { + Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + base.LoadEditor(); + } + + [Test] + public void TestBasicGameplayTest() + { + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + AddStep("exit player", () => editorPlayer.Exit()); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + } + + [Test] + public void TestCancelGameplayTestWithUnsavedChanges() + { + AddStep("delete all but first object", () => EditorBeatmap.RemoveRange(EditorBeatmap.HitObjects.Skip(1).ToList())); + + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveBeforeGameplayTestDialog); + + AddStep("dismiss prompt", () => + { + var button = DialogOverlay.CurrentDialog.Buttons.Last(); + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + AddWaitStep("wait some", 3); + AddAssert("stayed in editor", () => Stack.CurrentScreen is Editor); + } + + [Test] + public void TestSaveChangesBeforeGameplayTest() + { + AddStep("delete all but first object", () => EditorBeatmap.RemoveRange(EditorBeatmap.HitObjects.Skip(1).ToList())); + // bit of a hack to ensure this test can be ran multiple times without running into UNIQUE constraint failures + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = Guid.NewGuid().ToString()); + + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveBeforeGameplayTestDialog); + + AddStep("save changes", () => DialogOverlay.CurrentDialog.PerformOkAction()); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + AddAssert("beatmap has 1 object", () => editorPlayer.Beatmap.Value.Beatmap.HitObjects.Count == 1); + AddStep("exit player", () => editorPlayer.Exit()); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + } + + public override void TearDownSteps() + { + base.TearDownSteps(); + AddStep("delete imported", () => + { + beatmaps.Delete(importedBeatmapSet); + }); + } } } From 385df51b06d83e72bce2b3cc6b4a2d545e9f614b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 15:25:58 +0100 Subject: [PATCH 116/169] Ensure editor test player is exited on completion --- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 3 +-- osu.Game/Screens/Edit/EditorPlayer.cs | 11 +++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 9c44a6a986..e2a36e1371 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -106,8 +106,7 @@ namespace osu.Game.Tests.Visual.Editing EditorPlayer editorPlayer = null; AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); AddAssert("beatmap has 1 object", () => editorPlayer.Beatmap.Value.Beatmap.HitObjects.Count == 1); - AddStep("exit player", () => editorPlayer.Exit()); - AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + AddUntilStep("wait for return to editor", () => Stack.CurrentScreen is Editor); } public override void TearDownSteps() diff --git a/osu.Game/Screens/Edit/EditorPlayer.cs b/osu.Game/Screens/Edit/EditorPlayer.cs index f8dd75d94a..15e2ecb73b 100644 --- a/osu.Game/Screens/Edit/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/EditorPlayer.cs @@ -1,6 +1,7 @@ // 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.Screens; using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit @@ -12,6 +13,16 @@ namespace osu.Game.Screens.Edit { } + protected override void LoadComplete() + { + base.LoadComplete(); + ScoreProcessor.HasCompleted.BindValueChanged(completed => + { + if (completed.NewValue) + Scheduler.AddDelayed(this.Exit, RESULTS_DISPLAY_DELAY); + }); + } + protected override void PrepareReplay() { // don't record replays. From c465bcb82104a2e1739348daa89f140bedf7461c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 15:45:10 +0100 Subject: [PATCH 117/169] Ensure track is stopped on player completion --- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 2 ++ osu.Game/Screens/Edit/EditorPlayer.cs | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index e2a36e1371..db42667033 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -106,7 +106,9 @@ namespace osu.Game.Tests.Visual.Editing EditorPlayer editorPlayer = null; AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); AddAssert("beatmap has 1 object", () => editorPlayer.Beatmap.Value.Beatmap.HitObjects.Count == 1); + AddUntilStep("wait for return to editor", () => Stack.CurrentScreen is Editor); + AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning); } public override void TearDownSteps() diff --git a/osu.Game/Screens/Edit/EditorPlayer.cs b/osu.Game/Screens/Edit/EditorPlayer.cs index 15e2ecb73b..b2fab3fefc 100644 --- a/osu.Game/Screens/Edit/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/EditorPlayer.cs @@ -1,7 +1,9 @@ // 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.Screens; +using osu.Game.Overlays; using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit @@ -13,6 +15,9 @@ namespace osu.Game.Screens.Edit { } + [Resolved] + private MusicController musicController { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -29,5 +34,11 @@ namespace osu.Game.Screens.Edit } protected override bool CheckModsAllowFailure() => false; // never fail. + + public override bool OnExiting(IScreen next) + { + musicController.Stop(); + return base.OnExiting(next); + } } } From 459ec7b3bf4c60b9521629eead843dc9ebaea2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 20:42:33 +0100 Subject: [PATCH 118/169] Fix test failures due to missing dependencies --- osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs | 4 ++++ osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs index c3c803ff23..03e78ce854 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Edit; @@ -23,6 +24,9 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + public TestSceneSetupScreen() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index f961fff1e5..4bbffbdc7a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; @@ -18,6 +19,9 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + protected override bool ScrollUsingMouseWheel => false; public TestSceneTimingScreen() From 27707d52ec3815d66cdcbb4ebcabca90caf4453c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 1 Nov 2021 19:37:29 +0100 Subject: [PATCH 119/169] Implement slider-to-stream conversion --- .../Sliders/SliderSelectionBlueprint.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a7fadfb67f..ecf134dd15 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -47,6 +48,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } + [Resolved(CanBeNull = true)] + private BindableBeatDivisor beatDivisor { get; set; } + public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad; private readonly BindableList controlPoints = new BindableList(); @@ -173,6 +177,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (!IsSelected) + return false; + + if (e.Key == Key.F && e.ControlPressed && e.ShiftPressed) + { + convertToStream(); + return true; + } + + return false; + } + private int addControlPoint(Vector2 position) { position -= HitObject.Position; @@ -234,9 +252,42 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders editorBeatmap?.Update(HitObject); } + private void convertToStream() + { + if (editorBeatmap == null || changeHandler == null || beatDivisor == null) + return; + + var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime); + double streamSpacing = timingPoint.BeatLength / beatDivisor.Value; + + changeHandler.BeginChange(); + + int i = 0; + double time = HitObject.StartTime; + + while (!Precision.DefinitelyBigger(time, HitObject.GetEndTime(), 1)) + { + Vector2 position = HitObject.Position + HitObject.Path.PositionAt((time - HitObject.StartTime) / HitObject.Duration); + editorBeatmap.Add(new HitCircle + { + StartTime = time, + Position = position, + NewCombo = i == 0 && HitObject.NewCombo + }); + + i += 1; + time = HitObject.StartTime + i * streamSpacing; + } + + editorBeatmap.Remove(HitObject); + + changeHandler.EndChange(); + } + public override MenuItem[] ContextMenuItems => new MenuItem[] { new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), + new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream), }; // Always refer to the drawable object's slider body so subsequent movement deltas are calculated with updated positions. From d9494d405ef5b12000a8a1a4a412ef0c21af3545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 1 Nov 2021 19:37:37 +0100 Subject: [PATCH 120/169] Add test coverage for slider-to-stream conversion --- .../Editor/TestSceneSliderStreamConversion.cs | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs new file mode 100644 index 0000000000..f8cbd046d1 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs @@ -0,0 +1,140 @@ +// 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.Utils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneSliderStreamConversion : TestSceneOsuEditor + { + private BindableBeatDivisor beatDivisor => (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor)); + + [Test] + public void TestSimpleConversion() + { + Slider slider = null; + + AddStep("select first slider", () => + { + slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + + convertToStream(); + + AddAssert("stream created", () => streamCreatedFor(slider, 1 / 4d)); + + AddStep("undo", () => Editor.Undo()); + AddAssert("slider restored", () => sliderRestored(slider)); + + AddStep("select first slider", () => + { + slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + AddStep("change beat divisor", () => beatDivisor.Value = 8); + + convertToStream(); + AddAssert("stream created", () => streamCreatedFor(slider, 1 / 8d)); + } + + [Test] + public void TestConversionWithNonMatchingDivisor() + { + Slider slider = null; + + AddStep("select second slider", () => + { + slider = (Slider)EditorBeatmap.HitObjects.Where(h => h is Slider).ElementAt(1); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + AddStep("change beat divisor", () => beatDivisor.Value = 3); + + convertToStream(); + + AddAssert("stream created", () => streamCreatedFor(slider, 2 / 3d)); + } + + [Test] + public void TestConversionPreservesNewCombo() + { + Slider slider = null; + + AddStep("select second new-combo-starting slider", () => + { + slider = (Slider)EditorBeatmap.HitObjects.Where(h => h is Slider s && s.NewCombo).ElementAt(1); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + + convertToStream(); + + AddAssert("stream created", () => streamCreatedFor(slider, 1 / 4d)); + + AddStep("undo", () => Editor.Undo()); + AddAssert("slider restored", () => sliderRestored(slider)); + } + + private void convertToStream() + { + AddStep("convert to stream", () => + { + InputManager.PressKey(Key.LControl); + InputManager.PressKey(Key.LShift); + InputManager.Key(Key.F); + InputManager.ReleaseKey(Key.LShift); + InputManager.ReleaseKey(Key.LControl); + }); + } + + private bool streamCreatedFor(Slider slider, double spacing) + { + if (EditorBeatmap.HitObjects.Contains(slider)) + return false; + + for (int i = 0; i * spacing <= 1; ++i) + { + double progress = i * spacing; + double time = slider.StartTime + progress * slider.Duration; + Vector2 position = slider.Position + slider.Path.PositionAt(progress); + + if (!EditorBeatmap.HitObjects.OfType().Any(h => matches(h, time, position, slider.NewCombo && progress == 0))) + return false; + } + + return true; + + bool matches(HitCircle circle, double time, Vector2 position, bool startsNewCombo) => + Precision.AlmostEquals(circle.StartTime, time, 1) + && Precision.AlmostEquals(circle.Position, position, 0.01f) + && circle.NewCombo == startsNewCombo; + } + + private bool sliderRestored(Slider slider) + { + var objects = EditorBeatmap.HitObjects.Where(h => h.StartTime >= slider.StartTime && h.GetEndTime() <= slider.EndTime).ToList(); + + if (objects.Count > 1) + return false; + + var hitObject = objects.Single(); + if (!(hitObject is Slider restoredSlider)) + return false; + + return Precision.AlmostEquals(slider.StartTime, restoredSlider.StartTime) + && Precision.AlmostEquals(slider.GetEndTime(), restoredSlider.GetEndTime()) + && Precision.AlmostEquals(slider.Position, restoredSlider.Position, 0.01f) + && Precision.AlmostEquals(slider.EndPosition, restoredSlider.EndPosition, 0.01f); + } + } +} From 0cd3f9859817f354076d766745463ab7e1d73436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 21:43:06 +0100 Subject: [PATCH 121/169] Ensure samples & sample points are carried over during conversion --- .../Editor/TestSceneSliderStreamConversion.cs | 6 ++++-- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 9 ++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs index f8cbd046d1..11ab05c84e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } [Test] - public void TestConversionPreservesNewCombo() + public void TestConversionPreservesSliderProperties() { Slider slider = null; @@ -117,7 +117,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor bool matches(HitCircle circle, double time, Vector2 position, bool startsNewCombo) => Precision.AlmostEquals(circle.StartTime, time, 1) && Precision.AlmostEquals(circle.Position, position, 0.01f) - && circle.NewCombo == startsNewCombo; + && circle.NewCombo == startsNewCombo + && circle.Samples.SequenceEqual(slider.HeadCircle.Samples) + && circle.SampleControlPoint.IsRedundant(slider.SampleControlPoint); } private bool sliderRestored(Slider slider) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index ecf134dd15..d646d90d99 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -268,11 +269,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders while (!Precision.DefinitelyBigger(time, HitObject.GetEndTime(), 1)) { Vector2 position = HitObject.Position + HitObject.Path.PositionAt((time - HitObject.StartTime) / HitObject.Duration); + + var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone(); + samplePoint.Time = time; + editorBeatmap.Add(new HitCircle { StartTime = time, Position = position, - NewCombo = i == 0 && HitObject.NewCombo + NewCombo = i == 0 && HitObject.NewCombo, + SampleControlPoint = samplePoint, + Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList() }); i += 1; From 8aa04864cef399d3408202bba6ddc9b202da0aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Nov 2021 22:20:16 +0100 Subject: [PATCH 122/169] Add support for converting sliders with repeats to streams --- .../Editor/TestSceneSliderStreamConversion.cs | 63 ++++++++++++++++--- .../Sliders/SliderSelectionBlueprint.cs | 11 +++- 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs index 11ab05c84e..559d612037 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs @@ -30,7 +30,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor convertToStream(); - AddAssert("stream created", () => streamCreatedFor(slider, 1 / 4d)); + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 0.25, pathPosition: 0.25), + (time: 0.5, pathPosition: 0.5), + (time: 0.75, pathPosition: 0.75), + (time: 1, pathPosition: 1))); AddStep("undo", () => Editor.Undo()); AddAssert("slider restored", () => sliderRestored(slider)); @@ -44,7 +49,16 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("change beat divisor", () => beatDivisor.Value = 8); convertToStream(); - AddAssert("stream created", () => streamCreatedFor(slider, 1 / 8d)); + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 0.125, pathPosition: 0.125), + (time: 0.25, pathPosition: 0.25), + (time: 0.375, pathPosition: 0.375), + (time: 0.5, pathPosition: 0.5), + (time: 0.625, pathPosition: 0.625), + (time: 0.75, pathPosition: 0.75), + (time: 0.875, pathPosition: 0.875), + (time: 1, pathPosition: 1))); } [Test] @@ -62,7 +76,32 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor convertToStream(); - AddAssert("stream created", () => streamCreatedFor(slider, 2 / 3d)); + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 2 / 3d, pathPosition: 2 / 3d))); + } + + [Test] + public void TestConversionWithRepeats() + { + Slider slider = null; + + AddStep("select first slider with repeats", () => + { + slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider s && s.RepeatCount > 0); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + AddStep("change beat divisor", () => beatDivisor.Value = 2); + + convertToStream(); + + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 0.25, pathPosition: 0.5), + (time: 0.5, pathPosition: 1), + (time: 0.75, pathPosition: 0.5), + (time: 1, pathPosition: 0))); } [Test] @@ -79,7 +118,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor convertToStream(); - AddAssert("stream created", () => streamCreatedFor(slider, 1 / 4d)); + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 0.25, pathPosition: 0.25), + (time: 0.5, pathPosition: 0.5), + (time: 0.75, pathPosition: 0.75), + (time: 1, pathPosition: 1))); AddStep("undo", () => Editor.Undo()); AddAssert("slider restored", () => sliderRestored(slider)); @@ -97,18 +141,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); } - private bool streamCreatedFor(Slider slider, double spacing) + private bool streamCreatedFor(Slider slider, params (double time, double pathPosition)[] expectedCircles) { if (EditorBeatmap.HitObjects.Contains(slider)) return false; - for (int i = 0; i * spacing <= 1; ++i) + foreach ((double expectedTime, double expectedPathPosition) in expectedCircles) { - double progress = i * spacing; - double time = slider.StartTime + progress * slider.Duration; - Vector2 position = slider.Position + slider.Path.PositionAt(progress); + double time = slider.StartTime + slider.Duration * expectedTime; + Vector2 position = slider.Position + slider.Path.PositionAt(expectedPathPosition); - if (!EditorBeatmap.HitObjects.OfType().Any(h => matches(h, time, position, slider.NewCombo && progress == 0))) + if (!EditorBeatmap.HitObjects.OfType().Any(h => matches(h, time, position, slider.NewCombo && expectedTime == 0))) return false; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index d646d90d99..17a62fc61c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -16,6 +16,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -268,7 +269,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders while (!Precision.DefinitelyBigger(time, HitObject.GetEndTime(), 1)) { - Vector2 position = HitObject.Position + HitObject.Path.PositionAt((time - HitObject.StartTime) / HitObject.Duration); + // positionWithRepeats is a fractional number in the range of [0, HitObject.SpanCount()] + // and indicates how many fractional spans of a slider have passed up to time. + double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount(); + double pathPosition = positionWithRepeats - (int)positionWithRepeats; + // every second span is in the reverse direction - need to reverse the path position. + if (Precision.AlmostBigger(positionWithRepeats % 2, 1)) + pathPosition = 1 - pathPosition; + + Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition); var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone(); samplePoint.Time = time; From cdfe022805d7c0601460780f9824b05059c00c44 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Fri, 12 Nov 2021 00:56:08 +0000 Subject: [PATCH 123/169] Fix potential NaN values --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 6908521dff..6ccda57aad 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; - double sliderFactor = aimRatingNoSliders / aimRating; + double sliderFactor = (aimRating > 0) ? aimRatingNoSliders / aimRating : 1; if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; From e31ea49dd41091c0252b01776ce71829ba023436 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 13:18:46 +0900 Subject: [PATCH 124/169] Use fixed width font for performance points counter Matches all other display counters for in-game metrics. As mentioned in https://github.com/ppy/osu/discussions/15584. --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index ef289c2a20..e09adad081 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Play.HUD { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.Numeric.With(size: 16) + Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) }, new OsuSpriteText { From b9f9c27770b8d14933d6a2adbf3f3b6e56354983 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 13:59:22 +0900 Subject: [PATCH 125/169] Standardise spacing and padding between UR and PP counters --- osu.Game/Screens/Play/HUD/UnstableRateCounter.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 6cd9ca6154..235f0f01fd 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Screens.Play.HUD { @@ -98,6 +99,7 @@ namespace osu.Game.Screens.Play.HUD InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, + Spacing = new Vector2(2), Children = new Drawable[] { text = new OsuSpriteText @@ -111,8 +113,8 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), - Text = "UR", - Padding = new MarginPadding { Bottom = 1.5f }, + Text = @"UR", + Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better } } }; From 512094c17bd2511b3c06de55ac4c7b3a51e83ee7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 12 Nov 2021 14:00:36 +0900 Subject: [PATCH 126/169] Update tests --- .../OsuDifficultyCalculatorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index b0e173e752..9148f0715c 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.6975550434910005d, "diffcalc-test")] - [TestCase(1.4670676815251105d, "zero-length-sliders")] + [TestCase(6.6972307565739273d, "diffcalc-test")] + [TestCase(1.4484754139145539d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.9389769779826267d, "diffcalc-test")] - [TestCase(1.7786917985891204d, "zero-length-sliders")] + [TestCase(8.9382559208689809d, "diffcalc-test")] + [TestCase(1.7548875851757628d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); From 321aa456a785ce47bf2bdf44cf11faa82beaac7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 14:04:34 +0900 Subject: [PATCH 127/169] Adjust button size slightly --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a67ef070f6..d7e77b7fbf 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -267,7 +267,7 @@ namespace osu.Game.Screens.Edit new Dimension(GridSizeMode.Absolute, 220), new Dimension(), new Dimension(GridSizeMode.Absolute, 220), - new Dimension(GridSizeMode.Absolute, 160), + new Dimension(GridSizeMode.Absolute, 120), }, Content = new[] { From e891c0ce5362504d95d25120b5a449c5583596d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 14:13:11 +0900 Subject: [PATCH 128/169] Add keyboard shortcut to start test mode in editor --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++++- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 5 +++++ osu.Game/Screens/Edit/Editor.cs | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 22446634c1..c71cb6a00a 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -76,6 +76,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), + new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), }; public IEnumerable InGameKeyBindings => new[] @@ -288,6 +289,9 @@ namespace osu.Game.Input.Bindings ToggleChatFocus, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))] - EditorCycleGridDisplayMode + EditorCycleGridDisplayMode, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] + EditorTestGameplay } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 06f1b094bf..35a0c2ae74 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -169,6 +169,11 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode"); + /// + /// "Test gameplay" + /// + public static LocalisableString EditorTestGameplay => new TranslatableString(getKey(@"editor_test_gameplay"), @"Test gameplay"); + /// /// "Hold for HUD" /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d7e77b7fbf..77f097b79a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -468,6 +468,10 @@ namespace osu.Game.Screens.Edit menuBar.Mode.Value = EditorScreenMode.Verify; return true; + case GlobalAction.EditorTestGameplay: + testGameplay(); + return true; + default: return false; } From 113c95f3f55104856c44fc2a0ee0ba82f747f168 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 12 Nov 2021 14:22:43 +0900 Subject: [PATCH 129/169] Only apply high-pass temporarily --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 734caa43d4..fd54c10d86 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -356,7 +356,7 @@ namespace osu.Game.Screens.Play content.FadeInFromZero(400); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); - highPassFilter.CutoffTo(150); + highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in) ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); } From 54ae307a3d7ca38733065a7556a0978312d0cec1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 14:42:33 +0900 Subject: [PATCH 130/169] Trigger test via button click when using keyboard shortcut --- osu.Game/Screens/Edit/Editor.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 77f097b79a..738f607cc8 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -91,6 +91,8 @@ namespace osu.Game.Screens.Edit private DependencyContainer dependencies; + private TestGameplayButton testGameplayButton; + private bool isNewBeatmap; protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); @@ -289,7 +291,7 @@ namespace osu.Game.Screens.Edit Padding = new MarginPadding { Left = 10 }, Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, }, - new TestGameplayButton + testGameplayButton = new TestGameplayButton { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 10 }, @@ -469,7 +471,7 @@ namespace osu.Game.Screens.Edit return true; case GlobalAction.EditorTestGameplay: - testGameplay(); + testGameplayButton.TriggerClick(); return true; default: From 98dcf487da8b6eb14c7fea68ac38b171c760cf53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 15:25:59 +0900 Subject: [PATCH 131/169] Add fallback case for `GetDisplayString` if called on a null reference --- osu.Game.Tests/Models/DisplayStringTest.cs | 21 +++++++++++++++------ osu.Game/Extensions/ModelExtensions.cs | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Models/DisplayStringTest.cs b/osu.Game.Tests/Models/DisplayStringTest.cs index cac5dd1aaa..95af21eb5f 100644 --- a/osu.Game.Tests/Models/DisplayStringTest.cs +++ b/osu.Game.Tests/Models/DisplayStringTest.cs @@ -10,19 +10,28 @@ using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Users; +#nullable enable + namespace osu.Game.Tests.Models { [TestFixture] public class DisplayStringTest { + [Test] + public void TestNull() + { + IBeatmapSetInfo? beatmap = null; + Assert.That(beatmap.GetDisplayString(), Is.EqualTo("null")); + } + [Test] public void TestBeatmapSet() { var mock = new Mock(); - mock.Setup(m => m.Metadata.Artist).Returns("artist"); - mock.Setup(m => m.Metadata.Title).Returns("title"); - mock.Setup(m => m.Metadata.Author.Username).Returns("author"); + mock.Setup(m => m.Metadata!.Artist).Returns("artist"); + mock.Setup(m => m.Metadata!.Title).Returns("title"); + mock.Setup(m => m.Metadata!.Author.Username).Returns("author"); Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)")); } @@ -32,9 +41,9 @@ namespace osu.Game.Tests.Models { var mock = new Mock(); - mock.Setup(m => m.Metadata.Artist).Returns("artist"); - mock.Setup(m => m.Metadata.Title).Returns("title"); - mock.Setup(m => m.Metadata.Author.Username).Returns(string.Empty); + mock.Setup(m => m.Metadata!.Artist).Returns("artist"); + mock.Setup(m => m.Metadata!.Title).Returns("title"); + mock.Setup(m => m.Metadata!.Author.Username).Returns(string.Empty); Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title")); } diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs index 5c96add076..d8e0938d46 100644 --- a/osu.Game/Extensions/ModelExtensions.cs +++ b/osu.Game/Extensions/ModelExtensions.cs @@ -54,7 +54,7 @@ namespace osu.Game.Extensions } // fallback in case none of the above happens to match. - result ??= model.ToString(); + result ??= model?.ToString() ?? @"null"; return result; } } From ad8a710a690b3ee2888c145b171f9000bd21df3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 15:26:28 +0900 Subject: [PATCH 132/169] Fix failed imports being incorrectly considered as successfully importing for notification purposes --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/Database/IModelImporter.cs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 320f108886..e8b6996869 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -264,7 +264,7 @@ namespace osu.Game.Database model = CreateModel(archive); if (model == null) - return Task.FromResult>(new EntityFrameworkLive(null)); + return Task.FromResult>(null); } catch (TaskCanceledException) { diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index 5d0a044578..d00cfb2035 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -7,6 +7,8 @@ using System.Threading.Tasks; using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; +#nullable enable + namespace osu.Game.Database { /// @@ -26,7 +28,7 @@ namespace osu.Game.Database /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); + Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// Silently import an item from an . @@ -34,7 +36,7 @@ namespace osu.Game.Database /// The archive to be imported. /// Whether this is a low priority import. /// An optional cancellation token. - Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); + Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// Silently import an item from a . @@ -43,7 +45,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - Task> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); + Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// A user displayable name for the model type associated with this manager. From 5345018d4c98be6d4a82a2d571b073ee426fdbfc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 15:48:38 +0900 Subject: [PATCH 133/169] Add test coverage of failed imports --- .../Beatmaps/IO/ImportBeatmapTest.cs | 36 +++++++++++++++++++ .../Database/BeatmapImporterTests.cs | 29 +++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 3093a5719d..c206f874fd 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Scoring; @@ -387,6 +388,41 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public async Task TestModelCreationFailureDoesntReturn() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + { + try + { + var osu = LoadOsuIntoHost(host); + var importer = osu.Dependencies.Get(); + + var progressNotification = new ImportProgressNotification(); + + var zipStream = new MemoryStream(); + + using (var zip = ZipArchive.Create()) + zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate)); + + var imported = await importer.Import( + progressNotification, + new ImportTask(zipStream, string.Empty) + ); + + checkBeatmapSetCount(osu, 0); + checkBeatmapCount(osu, 0); + + Assert.IsEmpty(imported); + Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State); + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestRollbackOnFailure() { diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index e1fe1e224e..88d16c8a36 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -16,6 +16,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Models; +using osu.Game.Overlays.Notifications; using osu.Game.Stores; using osu.Game.Tests.Resources; using Realms; @@ -367,6 +368,34 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestModelCreationFailureDoesntReturn() + { + RunTestWithRealmAsync(async (realmFactory, storage) => + { + using var importer = new BeatmapImporter(realmFactory, storage); + using var store = new RealmRulesetStore(realmFactory, storage); + + var progressNotification = new ImportProgressNotification(); + + var zipStream = new MemoryStream(); + + using (var zip = ZipArchive.Create()) + zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate)); + + var imported = await importer.Import( + progressNotification, + new ImportTask(zipStream, string.Empty) + ); + + checkBeatmapSetCount(realmFactory.Context, 0); + checkBeatmapCount(realmFactory.Context, 0); + + Assert.IsEmpty(imported); + Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State); + }); + } + [Test] public void TestRollbackOnFailure() { From 9fc4bb70553dda53c8b56e1cdeaea39e6f193f32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 15:09:01 +0900 Subject: [PATCH 134/169] Fix incorrect xmldoc --- osu.Game/IO/Archives/LegacyByteArrayReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/Archives/LegacyByteArrayReader.cs b/osu.Game/IO/Archives/LegacyByteArrayReader.cs index 0c3620403f..ea8ff3bbe0 100644 --- a/osu.Game/IO/Archives/LegacyByteArrayReader.cs +++ b/osu.Game/IO/Archives/LegacyByteArrayReader.cs @@ -7,7 +7,7 @@ using System.IO; namespace osu.Game.IO.Archives { /// - /// Allows reading a single file from the provided stream. + /// Allows reading a single file from the provided byte array. /// public class LegacyByteArrayReader : ArchiveReader { From adf81d7fcd024e30a943a59d3f7ed9cc3584c3c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 16:39:17 +0900 Subject: [PATCH 135/169] Add pathway to correctly handle stream-based imports which are not zip archives --- osu.Game/Database/ImportTask.cs | 26 +++++++++++++++++++++++--- osu.Game/Utils/ZipUtils.cs | 28 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index 1433a567a9..1fb5a42630 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -47,10 +47,30 @@ namespace osu.Game.Database /// public ArchiveReader GetReader() { - if (Stream != null) - return new ZipArchiveReader(Stream, Path); + return Stream != null + ? getReaderFrom(Stream) + : getReaderFrom(Path); + } - return getReaderFrom(Path); + /// + /// Creates an from a stream. + /// + /// A seekable stream containing the archive content. + /// A reader giving access to the archive's content. + private ArchiveReader getReaderFrom(Stream stream) + { + if (!(stream is MemoryStream memoryStream)) + { + // This isn't used in any current path. May need to reconsider for performance reasons (ie. if we don't expect the incoming stream to be copied out). + byte[] buffer = new byte[stream.Length]; + stream.Read(buffer, 0, (int)stream.Length); + memoryStream = new MemoryStream(buffer); + } + + if (ZipUtils.IsZipArchive(memoryStream)) + return new ZipArchiveReader(memoryStream, Path); + + return new LegacyByteArrayReader(memoryStream.ToArray(), Path); } /// diff --git a/osu.Game/Utils/ZipUtils.cs b/osu.Game/Utils/ZipUtils.cs index cd4d876451..16eb1c7e4e 100644 --- a/osu.Game/Utils/ZipUtils.cs +++ b/osu.Game/Utils/ZipUtils.cs @@ -9,6 +9,34 @@ namespace osu.Game.Utils { public static class ZipUtils { + public static bool IsZipArchive(Stream stream) + { + try + { + stream.Seek(0, SeekOrigin.Begin); + + using (var arc = ZipArchive.Open(stream)) + { + foreach (var entry in arc.Entries) + { + using (entry.OpenEntryStream()) + { + } + } + } + + return true; + } + catch (Exception) + { + return false; + } + finally + { + stream.Seek(0, SeekOrigin.Begin); + } + } + public static bool IsZipArchive(string path) { if (!File.Exists(path)) From 9fb2402781ad91c197d51aeec716b0000f52c4d1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 12 Nov 2021 17:31:25 +0900 Subject: [PATCH 136/169] Remove unnecessary parens --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 6ccda57aad..558ddc16ef 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; - double sliderFactor = (aimRating > 0) ? aimRatingNoSliders / aimRating : 1; + double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; From 6a098a86342505dcc6dbafdbb4b46cce784f4c38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 17:45:05 +0900 Subject: [PATCH 137/169] Rename `BeatmapInfo.OnlineBeatmapID` to `OnlineID` to match interface --- .../Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Beatmaps/IO/ImportBeatmapTest.cs | 12 ++++++------ .../NonVisual/Filtering/FilterMatchingTest.cs | 2 +- .../Gameplay/TestScenePlayerScoreSubmission.cs | 2 +- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- .../TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- ...SceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestScenePlaylistsSongSelect.cs | 2 +- .../Navigation/TestScenePresentBeatmap.cs | 8 ++++---- .../Visual/Navigation/TestScenePresentScore.cs | 4 ++-- .../Online/TestSceneNowPlayingCommand.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 2 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 2 +- .../SongSelect/TestSceneBeatmapCarousel.cs | 4 ++-- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../TestSceneBeatmapRecommendations.cs | 6 +++--- .../SongSelect/TestScenePlaySongSelect.cs | 14 +++++++------- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 11 ++++++----- osu.Game/Beatmaps/BeatmapModelManager.cs | 18 +++++++++--------- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 12 ++++++------ .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Database/OsuDbContext.cs | 2 +- osu.Game/Online/Chat/NowPlayingCommand.cs | 2 +- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- .../Panels/BeatmapPanelDownloadButton.cs | 2 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- osu.Game/Screens/Play/SoloPlayer.cs | 6 +++--- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- .../Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- .../Select/Carousel/DrawableCarouselBeatmap.cs | 4 ++-- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 4 ++-- osu.Game/Stores/BeatmapImporter.cs | 6 +++--- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 2 +- .../Multiplayer/TestMultiplayerClient.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 44 files changed, 86 insertions(+), 85 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 304a65e5c7..9dc144927b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -116,7 +116,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("Insane", beatmapInfo.DifficultyName); Assert.AreEqual(string.Empty, metadata.Source); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); - Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID); + Assert.AreEqual(557821, beatmapInfo.OnlineID); Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineBeatmapSetID); } } diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index c206f874fd..10b9f5e0e2 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -542,7 +542,7 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await LoadOszIntoOsu(osu); foreach (var b in imported.Beatmaps) - b.OnlineBeatmapID = null; + b.OnlineID = null; osu.Dependencies.Get().Update(imported); @@ -587,13 +587,13 @@ namespace osu.Game.Tests.Beatmaps.IO { new BeatmapInfo { - OnlineBeatmapID = 2, + OnlineID = 2, Metadata = metadata, BaseDifficulty = difficulty }, new BeatmapInfo { - OnlineBeatmapID = 2, + OnlineID = 2, Metadata = metadata, Status = BeatmapSetOnlineStatus.Loved, BaseDifficulty = difficulty @@ -606,8 +606,8 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await manager.Import(toImport); Assert.NotNull(imported); - Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineBeatmapID); - Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineBeatmapID); + Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineID); + Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineID); } finally { @@ -1078,7 +1078,7 @@ namespace osu.Game.Tests.Beatmaps.IO var set = queryBeatmapSets().First(); foreach (BeatmapInfo b in set.Beatmaps) - Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID)); + Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID)); Assert.IsTrue(set.Beatmaps.Count > 0); var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Any() == true); diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index ec97948532..1bc663a1f9 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -207,7 +207,7 @@ namespace osu.Game.Tests.NonVisual.Filtering public void TestCriteriaMatchingBeatmapIDs(string query, bool filtered) { var beatmap = getExampleBeatmap(); - beatmap.OnlineBeatmapID = 20201010; + beatmap.OnlineID = 20201010; beatmap.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = 1535 }; var criteria = new FilterCriteria { SearchText = query }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index bf864f844c..cb5058779c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Gameplay createPlayerTest(false, r => { var beatmap = createTestBeatmap(r); - beatmap.BeatmapInfo.OnlineBeatmapID = null; + beatmap.BeatmapInfo.OnlineID = null; return beatmap; }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 20e859dd2b..9fadbe02bd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import beatmap", () => { importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; - importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineBeatmapID ?? -1; + importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index fdd01446b9..b10856b704 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); - importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1; + importedBeatmapId = importedBeatmap.OnlineID ?? -1; } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 902629765f..25200560e4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { - SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID ?? 0); multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true)); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index af4e696fce..16a342df8c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { - SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID ?? 0); var roomUser = OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true); roomUser.MatchState = new TeamVersusUserState diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 0d6b428cce..1f96a15f80 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmaps.Add(new BeatmapInfo { Ruleset = rulesets.GetRuleset(i % 4), - OnlineBeatmapID = beatmapId, + OnlineID = beatmapId, Length = length, BPM = bpm, BaseDifficulty = new BeatmapDifficulty() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 5e55759e01..07ce6ebffc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmaps.Add(new BeatmapInfo { Ruleset = new OsuRuleset().RulesetInfo, - OnlineBeatmapID = beatmapId, + OnlineID = beatmapId, DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 5f5ebfccfb..fc4679b2e3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -113,14 +113,14 @@ namespace osu.Game.Tests.Visual.Navigation { new BeatmapInfo { - OnlineBeatmapID = i * 1024, + OnlineID = i * 1024, Metadata = metadata, BaseDifficulty = difficulty, Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, new BeatmapInfo { - OnlineBeatmapID = i * 2048, + OnlineID = i * 2048, Metadata = metadata, BaseDifficulty = difficulty, Ruleset = ruleset ?? new OsuRuleset().RulesetInfo @@ -145,11 +145,11 @@ namespace osu.Game.Tests.Visual.Navigation private void presentSecondDifficultyAndConfirm(Func getImport, int importedID) { - Predicate pred = b => b.OnlineBeatmapID == importedID * 2048; + Predicate pred = b => b.OnlineID == importedID * 2048; AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred)); AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); - AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineBeatmapID == importedID * 2048); + AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID == importedID * 2048); AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Beatmaps.First().Ruleset.ID); } } diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index aca7ada535..062acb4d2c 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -45,14 +45,14 @@ namespace osu.Game.Tests.Visual.Navigation { new BeatmapInfo { - OnlineBeatmapID = 1 * 1024, + OnlineID = 1 * 1024, Metadata = metadata, BaseDifficulty = difficulty, Ruleset = new OsuRuleset().RulesetInfo }, new BeatmapInfo { - OnlineBeatmapID = 1 * 2048, + OnlineID = 1 * 2048, Metadata = metadata, BaseDifficulty = difficulty, Ruleset = new OsuRuleset().RulesetInfo diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs index 366fa8a4af..7a5ee84eb4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null) { - BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null } + BeatmapInfo = { OnlineID = hasOnlineId ? 1234 : (int?)null } }); AddStep("Run command", () => Add(new NowPlayingCommand())); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index cda7e95a46..f3ee01354c 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Playlists beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; // intentionally increment online IDs to clash with import below. - beatmap.BeatmapInfo.OnlineBeatmapID++; + beatmap.BeatmapInfo.OnlineID++; beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID++; importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 8d5d0ba8c7..423c0a048c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -337,7 +337,7 @@ namespace osu.Game.Tests.Visual.Ranking public UnrankedSoloResultsScreen(ScoreInfo score) : base(score, true) { - Score.BeatmapInfo.OnlineBeatmapID = 0; + Score.BeatmapInfo.OnlineID = 0; Score.BeatmapInfo.Status = BeatmapSetOnlineStatus.Pending; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 03079fdc5f..1c5898d073 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -867,7 +867,7 @@ namespace osu.Game.Tests.Visual.SongSelect yield return new BeatmapInfo { - OnlineBeatmapID = id++ * 10, + OnlineID = id++ * 10, DifficultyName = version, StarRating = diff, Ruleset = new OsuRuleset().RulesetInfo, @@ -900,7 +900,7 @@ namespace osu.Game.Tests.Visual.SongSelect { toReturn.Beatmaps.Add(new BeatmapInfo { - OnlineBeatmapID = b * 10, + OnlineID = b * 10, Path = $"extra{b}.osu", DifficultyName = $"Extra {b}", Ruleset = rulesets.GetRuleset((b - 1) % 4), diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 855a59b5f5..a8a7a5d350 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -388,7 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect { leaderboard.BeatmapInfo = new BeatmapInfo { - OnlineBeatmapID = 1113057, + OnlineID = 1113057, Status = status, }; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 57f2d436c5..cbb58a916f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual.SongSelect Metadata = metadata, Beatmaps = difficultyRulesets.Select((ruleset, difficultyIndex) => new BeatmapInfo { - OnlineBeatmapID = importID * 1024 + difficultyIndex, + OnlineID = importID * 1024 + difficultyIndex, Metadata = metadata, BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, @@ -205,8 +205,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); AddUntilStep("recommended beatmap displayed", () => { - int? expectedID = getImport().Beatmaps[expectedDiff - 1].OnlineBeatmapID; - return Game.Beatmap.Value.BeatmapInfo.OnlineBeatmapID == expectedID; + int? expectedID = getImport().Beatmaps[expectedDiff - 1].OnlineID; + return Game.Beatmap.Value.BeatmapInfo.OnlineID == expectedID; }); } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index a0d78fff58..b2fc34d675 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -507,13 +507,13 @@ namespace osu.Game.Tests.Visual.SongSelect i.IsFiltered || i.Item.BeatmapInfo.Ruleset.ID == targetRuleset || i.Item.BeatmapInfo.Ruleset.ID == 0); }); - AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID); - AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); + AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID); + AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID); AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = string.Empty); - AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); - AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); + AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineID == target.OnlineID); + AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineID == target.OnlineID); } [Test] @@ -544,8 +544,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); - AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID); - AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); + AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID); + AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID); AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nononoo"); @@ -918,7 +918,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmaps.Add(new BeatmapInfo { Ruleset = getRuleset(), - OnlineBeatmapID = beatmapId, + OnlineID = beatmapId, DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 035f438b89..7231409dc5 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -290,7 +290,7 @@ namespace osu.Game.Beatmaps catch (BeatmapInvalidForRulesetException e) { if (rulesetInfo.Equals(beatmapInfo.Ruleset)) - Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineBeatmapID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); + Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); return new StarDifficulty(); } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 5bbd48f26d..90c5b13bc4 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -23,13 +23,14 @@ namespace osu.Game.Beatmaps public int BeatmapVersion; - private int? onlineBeatmapID; + private int? onlineID; [JsonProperty("id")] - public int? OnlineBeatmapID + [Column("OnlineBeatmapID")] + public int? OnlineID { - get => onlineBeatmapID; - set => onlineBeatmapID = value > 0 ? value : null; + get => onlineID; + set => onlineID = value > 0 ? value : null; } [JsonIgnore] @@ -176,7 +177,7 @@ namespace osu.Game.Beatmaps #region Implementation of IHasOnlineID - public int OnlineID => OnlineBeatmapID ?? -1; + int IHasOnlineID.OnlineID => onlineID ?? -1; #endregion diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index ff4305dc91..ac94163b4d 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -94,13 +94,13 @@ namespace osu.Game.Beatmaps validateOnlineIds(beatmapSet); - bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0); + bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); if (OnlineLookupQueue != null) await OnlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken).ConfigureAwait(false); // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. - if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0)) + if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) { if (beatmapSet.OnlineBeatmapSetID != null) { @@ -127,7 +127,7 @@ namespace osu.Game.Beatmaps // in order to avoid a unique key constraint, immediately remove the online ID from the previous set. existingSetWithSameOnlineID.OnlineBeatmapSetID = null; foreach (var b in existingSetWithSameOnlineID.Beatmaps) - b.OnlineBeatmapID = null; + b.OnlineID = null; LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been deleted."); } @@ -136,7 +136,7 @@ namespace osu.Game.Beatmaps private void validateOnlineIds(BeatmapSetInfo beatmapSet) { - var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); + var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineID.HasValue).Select(b => b.OnlineID).ToList(); // ensure all IDs are unique if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) @@ -147,7 +147,7 @@ namespace osu.Game.Beatmaps } // find any existing beatmaps in the database that have matching online ids - var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).ToList(); + var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineID)).ToList(); if (existingBeatmaps.Count > 0) { @@ -162,7 +162,7 @@ namespace osu.Game.Beatmaps } } - void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null); + void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineID = null); } /// @@ -242,7 +242,7 @@ namespace osu.Game.Beatmaps if (!base.CanSkipImport(existing, import)) return false; - return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null); + return existing.Beatmaps.Any(b => b.OnlineID != null); } protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) @@ -250,8 +250,8 @@ namespace osu.Game.Beatmaps if (!base.CanReuseExisting(existing, import)) return false; - var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); - var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + var existingIds = existing.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i); + var importIds = import.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i); // force re-import if we are not in a sane state. return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index b05ad9a1dd..69b03683dd 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -85,7 +85,7 @@ namespace osu.Game.Beatmaps beatmapInfo.Status = res.Status; beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapSetOnlineStatus.None; beatmapInfo.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; - beatmapInfo.OnlineBeatmapID = res.OnlineID; + beatmapInfo.OnlineID = res.OnlineID; if (beatmapInfo.Metadata != null) beatmapInfo.Metadata.AuthorID = res.AuthorID; @@ -103,7 +103,7 @@ namespace osu.Game.Beatmaps void fail(Exception e) { - beatmapInfo.OnlineBeatmapID = null; + beatmapInfo.OnlineID = null; logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})"); } } @@ -161,7 +161,7 @@ namespace osu.Game.Beatmaps if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) && string.IsNullOrEmpty(beatmapInfo.Path) - && beatmapInfo.OnlineBeatmapID == null) + && beatmapInfo.OnlineID == null) return false; try @@ -172,10 +172,10 @@ namespace osu.Game.Beatmaps using (var cmd = db.CreateCommand()) { - cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path"; + cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); - cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmapInfo.OnlineBeatmapID ?? (object)DBNull.Value)); + cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID ?? (object)DBNull.Value)); cmd.Parameters.Add(new SqliteParameter("@Path", beatmapInfo.Path)); using (var reader = cmd.ExecuteReader()) @@ -187,7 +187,7 @@ namespace osu.Game.Beatmaps beatmapInfo.Status = status; beatmapInfo.BeatmapSet.Status = status; beatmapInfo.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0); - beatmapInfo.OnlineBeatmapID = reader.GetInt32(1); + beatmapInfo.OnlineID = reader.GetInt32(1); if (beatmapInfo.Metadata != null) beatmapInfo.Metadata.AuthorID = reader.GetInt32(3); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index f0c19f80f3..7a92d03e42 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -263,7 +263,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"BeatmapID": - beatmap.BeatmapInfo.OnlineBeatmapID = Parsing.ParseInt(pair.Value); + beatmap.BeatmapInfo.OnlineID = Parsing.ParseInt(pair.Value); break; case @"BeatmapSetID": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index bcb14526c7..e26b96254f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -133,7 +133,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.DifficultyName}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Source)) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Tags)) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); - if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}")); + if (beatmap.BeatmapInfo.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineID}")); if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}")); } diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 1d8322aadd..91654e2827 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -125,7 +125,7 @@ namespace osu.Game.Database { base.OnModelCreating(modelBuilder); - modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); modelBuilder.Entity().HasIndex(b => b.MD5Hash); modelBuilder.Entity().HasIndex(b => b.Hash); diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index adb3d88df6..34b12c23e6 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -57,7 +57,7 @@ namespace osu.Game.Online.Chat break; } - string beatmapString = beatmapInfo.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineBeatmapID} {beatmapInfo}]" : beatmapInfo.ToString(); + string beatmapString = beatmapInfo.OnlineID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineID} {beatmapInfo}]" : beatmapInfo.ToString(); channelManager.PostMessage($"is {verb} {beatmapString}", true, target); Expire(); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 567e59e8a0..68f5dfce6a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -229,7 +229,7 @@ namespace osu.Game.Online.Multiplayer { Value = new BeatmapInfo { - OnlineBeatmapID = Room.Settings.BeatmapID, + OnlineID = Room.Settings.BeatmapID, MD5Hash = Room.Settings.BeatmapChecksum } }, diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index eb9ea608f7..aa0e37363b 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -113,7 +113,7 @@ namespace osu.Game.Online.Rooms int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - return beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null; + return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null; } } } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index f9366674d8..6b95d288c5 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -144,7 +144,7 @@ namespace osu.Game.Online.Spectator IsPlaying = true; // transfer state at point of beginning play - currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineBeatmapID; + currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs index 5ed49cf384..8c7846783d 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels case DownloadState.LocallyAvailable: Predicate findPredicate = null; if (SelectedBeatmap.Value != null) - findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineID; + findPredicate = b => b.OnlineID == SelectedBeatmap.Value.OnlineID; game?.PresentBeatmap(beatmapSet, findPredicate); break; diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index d6016ec4b9..dc928d90e9 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -366,7 +366,7 @@ namespace osu.Game.Screens.OnlinePlay.Match var beatmap = SelectedItem.Value?.Beatmap.Value; // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info - var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineID); + var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index bdb5ff9bb2..22537c3ce0 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void load(IBindable ruleset) { // Sanity checks to ensure that PlaylistsPlayer matches the settings for the current PlaylistItem - if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != PlaylistItem.Beatmap.Value.OnlineID) + if (Beatmap.Value.BeatmapInfo.OnlineID != PlaylistItem.Beatmap.Value.OnlineID) throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); if (ruleset.Value.ID != PlaylistItem.Ruleset.Value.ID) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 675cb71311..6cea75af0a 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateTokenRequest() { - if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) + if (!(Beatmap.Value.BeatmapInfo.OnlineID is int beatmapId)) return null; if (!(Ruleset.Value.ID is int rulesetId) || Ruleset.Value.ID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) @@ -40,9 +40,9 @@ namespace osu.Game.Screens.Play { var beatmap = score.ScoreInfo.BeatmapInfo; - Debug.Assert(beatmap.OnlineBeatmapID != null); + Debug.Assert(beatmap.OnlineID != null); - int beatmapId = beatmap.OnlineBeatmapID.Value; + int beatmapId = beatmap.OnlineID.Value; return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); } diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 4f4dfa4909..425e6f983b 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { - if (Score.BeatmapInfo.OnlineBeatmapID == null || Score.BeatmapInfo.Status <= BeatmapSetOnlineStatus.Pending) + if (Score.BeatmapInfo.OnlineID == null || Score.BeatmapInfo.Status <= BeatmapSetOnlineStatus.Pending) return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 6f215b9287..7543c89f17 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -192,7 +192,7 @@ namespace osu.Game.Screens.Select return; } - // for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time). + // for now, let's early abort if an OnlineID is not present (should have been populated at import time). if (BeatmapInfo == null || BeatmapInfo.OnlineID <= 0 || api.State.Value == APIState.Offline) { updateMetrics(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 1334784613..df51287d4a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Select.Carousel // this should be done after text matching so we can prioritise matching numbers in metadata. if (!match && criteria.SearchNumber.HasValue) { - match = (BeatmapInfo.OnlineBeatmapID == criteria.SearchNumber.Value) || + match = (BeatmapInfo.OnlineID == criteria.SearchNumber.Value) || (BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID == criteria.SearchNumber.Value); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index f30bec5d2b..d0f9d835fd 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -238,8 +238,8 @@ namespace osu.Game.Screens.Select.Carousel if (editRequested != null) items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmapInfo))); - if (beatmapInfo.OnlineBeatmapID.HasValue && beatmapOverlay != null) - items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineBeatmapID.Value))); + if (beatmapInfo.OnlineID.HasValue && beatmapOverlay != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID.Value))); if (collectionManager != null) { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 9205c6c0d2..9c8ccee99b 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - if (BeatmapInfo.OnlineBeatmapID == null || BeatmapInfo?.Status <= BeatmapSetOnlineStatus.Pending) + if (BeatmapInfo.OnlineID == null || BeatmapInfo?.Status <= BeatmapSetOnlineStatus.Pending) { PlaceholderState = PlaceholderState.Unavailable; return null; diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 3bcfcb2a0b..1f07042ede 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Spectate if (!playingUserStates.TryGetValue(userId, out var userState)) continue; - if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID)) + if (beatmapSet.Beatmaps.Any(b => b.OnlineID == userState.BeatmapID)) updateGameplayState(userId); } } @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Spectate if (resolvedRuleset == null) return; - var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == spectatorState.BeatmapID); + var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineID == spectatorState.BeatmapID); if (resolvedBeatmap == null) return; diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 01bf925547..f203e55c27 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -63,7 +63,7 @@ namespace osu.Game.Stores validateOnlineIds(beatmapSet, realm); - bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); + bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); if (onlineLookupQueue != null) { @@ -72,7 +72,7 @@ namespace osu.Game.Stores } // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. - if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) + if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) { if (beatmapSet.OnlineID > 0) { @@ -254,7 +254,7 @@ namespace osu.Game.Stores { Hash = hash, DifficultyName = decodedInfo.DifficultyName, - OnlineID = decodedInfo.OnlineBeatmapID ?? -1, + OnlineID = decodedInfo.OnlineID ?? -1, AudioLeadIn = decodedInfo.AudioLeadIn, StackLeniency = decodedInfo.StackLeniency, SpecialStyle = decodedInfo.SpecialStyle, diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index b9eda5c06e..38c8219813 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.BeatmapSet.OnlineBeatmapSetID = Interlocked.Increment(ref onlineSetID); BeatmapInfo.Length = 75000; BeatmapInfo.OnlineInfo = new APIBeatmap(); - BeatmapInfo.OnlineBeatmapID = Interlocked.Increment(ref onlineBeatmapID); + BeatmapInfo.OnlineID = Interlocked.Increment(ref onlineBeatmapID); } protected virtual Beatmap CreateBeatmap() => createTestBeatmap(); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index f81ca70631..ac9be03cc1 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -287,7 +287,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == Room.RoomID); IBeatmapSetInfo? set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet - ?? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId)?.BeatmapSet; + ?? beatmaps.QueryBeatmap(b => b.OnlineID == beatmapId)?.BeatmapSet; if (set == null) throw new InvalidOperationException("Beatmap not found."); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index a4cef02395..83bf130f26 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -222,7 +222,7 @@ namespace osu.Game.Tests.Visual { new APIBeatmap { - OnlineID = beatmap.OnlineID, + OnlineID = ((IBeatmapInfo)beatmap).OnlineID, OnlineBeatmapSetID = beatmap.BeatmapSet.OnlineID, Status = beatmap.Status, Checksum = beatmap.MD5Hash, From 692e846acdbb188666e3a0cb2891337f0164a396 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 17:50:31 +0900 Subject: [PATCH 138/169] Rename `BeatmapSetInfo.OnlineBeatmapSetID` to `OnlineID` to match interface --- .../Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Beatmaps/IO/ImportBeatmapTest.cs | 8 ++++---- .../Beatmaps/IO/OszArchiveReaderTest.cs | 2 +- .../NonVisual/BeatmapSetInfoEqualityTest.cs | 8 ++++---- .../NonVisual/Filtering/FilterMatchingTest.cs | 2 +- .../Online/TestSceneBeatmapManager.cs | 2 +- ...ceneOnlinePlayBeatmapAvailabilityTracker.cs | 6 +++--- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestScenePlaylistsSongSelect.cs | 2 +- .../Navigation/TestScenePerformFromScreen.cs | 2 +- .../Navigation/TestScenePresentBeatmap.cs | 2 +- .../Visual/Navigation/TestScenePresentScore.cs | 2 +- .../Online/TestSceneDirectDownloadButton.cs | 4 ++-- .../TestScenePlaylistsRoomCreation.cs | 2 +- .../SongSelect/TestSceneBeatmapCarousel.cs | 4 ++-- .../TestSceneBeatmapRecommendations.cs | 2 +- .../SongSelect/TestScenePlaySongSelect.cs | 2 +- osu.Game/Beatmaps/BeatmapModelManager.cs | 18 +++++++++--------- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 4 ++-- osu.Game/Beatmaps/BeatmapSetInfo.cs | 15 ++++++++------- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Database/OsuDbContext.cs | 2 +- osu.Game/Online/BeatmapDownloadTracker.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Play/SoloSpectator.cs | 2 +- .../Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 4 ++-- osu.Game/Stores/BeatmapImporter.cs | 2 +- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 4 ++-- 32 files changed, 60 insertions(+), 59 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 9dc144927b..677aaf6f78 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(string.Empty, metadata.Source); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); Assert.AreEqual(557821, beatmapInfo.OnlineID); - Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineBeatmapSetID); + Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineID); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 37c1dfc657..bfd6ff0314 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = decodeAsJson(normal); var meta = beatmap.BeatmapInfo.Metadata; - Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID); + Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 10b9f5e0e2..e00d7a1115 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -581,7 +581,7 @@ namespace osu.Game.Tests.Beatmaps.IO var toImport = new BeatmapSetInfo { - OnlineBeatmapSetID = 1, + OnlineID = 1, Metadata = metadata, Beatmaps = new List { @@ -1056,13 +1056,13 @@ namespace osu.Game.Tests.Beatmaps.IO { IEnumerable resultSets = null; var store = osu.Dependencies.Get(); - waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(), + waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineID == 241526)).Any(), @"BeatmapSet did not import to the database in allocated time.", timeout); // ensure we were stored to beatmap database backing... Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); - IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0); - IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526); + IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineID == 241526 && s.BaseDifficultyID > 0); + IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineID == 241526); // if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. waitForOrAssert(() => queryBeatmaps().Count() == 12, diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index eaf5d107ca..b2ab1eeaa6 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.IO var meta = beatmap.Metadata; - Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID); + Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs index 9ce7e0a0e0..938edf07c6 100644 --- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -12,8 +12,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestOnlineWithOnline() { - var ourInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; - var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; + var ourInfo = new BeatmapSetInfo { OnlineID = 123 }; + var otherInfo = new BeatmapSetInfo { OnlineID = 123 }; Assert.AreEqual(ourInfo, otherInfo); } @@ -30,8 +30,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDatabasedWithOnline() { - var ourInfo = new BeatmapSetInfo { ID = 123, OnlineBeatmapSetID = 12 }; - var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 12 }; + var ourInfo = new BeatmapSetInfo { ID = 123, OnlineID = 12 }; + var otherInfo = new BeatmapSetInfo { OnlineID = 12 }; Assert.AreEqual(ourInfo, otherInfo); } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 1bc663a1f9..ee1feeca8d 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -208,7 +208,7 @@ namespace osu.Game.Tests.NonVisual.Filtering { var beatmap = getExampleBeatmap(); beatmap.OnlineID = 20201010; - beatmap.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = 1535 }; + beatmap.BeatmapSet = new BeatmapSetInfo { OnlineID = 1535 }; var criteria = new FilterCriteria { SearchText = query }; var carouselItem = new CarouselBeatmap(beatmap); diff --git a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs index 4d5bee13f2..fc1b4f224d 100644 --- a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs +++ b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Online private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo { - OnlineBeatmapSetID = 1, + OnlineID = 1, Metadata = new BeatmapMetadata { Artist = "test author", diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index e3e2304990..b66da028f1 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - var existing = beatmaps.QueryBeatmapSet(s => s.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID); + var existing = beatmaps.QueryBeatmapSet(s => s.OnlineID == testBeatmapSet.OnlineID); if (existing != null) beatmaps.Delete(existing); @@ -101,10 +101,10 @@ namespace osu.Game.Tests.Online AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); - AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID))); + AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID))); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID))); + AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID))); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 1f96a15f80..aef1fb31d6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer manager.Import(new BeatmapSetInfo { - OnlineBeatmapSetID = 10, + OnlineID = 10, Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Metadata = new BeatmapMetadata { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 07ce6ebffc..05f9a94cf7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer manager.Import(new BeatmapSetInfo { - OnlineBeatmapSetID = 10, + OnlineID = 10, Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 4ec76e1e4b..4e1b3bb9bf 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.Navigation { AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); PushAndConfirm(() => new TestPlaySongSelect()); - AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineBeatmapSetID == 241526); + AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526); } public class DialogBlockingScreen : OsuScreen diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index fc4679b2e3..ff976c7bf6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Navigation imported = Game.BeatmapManager.Import(new BeatmapSetInfo { Hash = Guid.NewGuid().ToString(), - OnlineBeatmapSetID = i, + OnlineID = i, Metadata = metadata, Beatmaps = new List { diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 062acb4d2c..72f160c9a9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Navigation beatmap = Game.BeatmapManager.Import(new BeatmapSetInfo { Hash = Guid.NewGuid().ToString(), - OnlineBeatmapSetID = 1, + OnlineID = 1, Metadata = metadata, Beatmaps = new List { diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index a865bbe950..22a91fa9a3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); - AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); + AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineID == 241526)); AddUntilStep("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); createButtonWithBeatmap(createSoleily()); @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("remove soleily", () => { - var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526); + var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526); if (beatmap != null) beatmaps.Delete(beatmap); }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index f3ee01354c..c5287d4257 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Playlists // intentionally increment online IDs to clash with import below. beatmap.BeatmapInfo.OnlineID++; - beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID++; + beatmap.BeatmapInfo.BeatmapSet.OnlineID++; importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value; }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 1c5898d073..534442c8b6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -838,7 +838,7 @@ namespace osu.Game.Tests.Visual.SongSelect return new BeatmapSetInfo { ID = id, - OnlineBeatmapSetID = id, + OnlineID = id, Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { @@ -884,7 +884,7 @@ namespace osu.Game.Tests.Visual.SongSelect var toReturn = new BeatmapSetInfo { ID = id, - OnlineBeatmapSetID = id, + OnlineID = id, Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index cbb58a916f..a0742b862b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -180,7 +180,7 @@ namespace osu.Game.Tests.Visual.SongSelect var beatmapSet = new BeatmapSetInfo { Hash = Guid.NewGuid().ToString(), - OnlineBeatmapSetID = importID, + OnlineID = importID, Metadata = metadata, Beatmaps = difficultyRulesets.Select((ruleset, difficultyIndex) => new BeatmapInfo { diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index b2fc34d675..ee5a61f21f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -931,7 +931,7 @@ namespace osu.Game.Tests.Visual.SongSelect return new BeatmapSetInfo { - OnlineBeatmapSetID = setId, + OnlineID = setId, Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index ac94163b4d..3becbee0ba 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -102,9 +102,9 @@ namespace osu.Game.Beatmaps // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) { - if (beatmapSet.OnlineBeatmapSetID != null) + if (beatmapSet.OnlineID != null) { - beatmapSet.OnlineBeatmapSetID = null; + beatmapSet.OnlineID = null; LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs"); } } @@ -116,20 +116,20 @@ namespace osu.Game.Beatmaps throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); // check if a set already exists with the same online id, delete if it does. - if (beatmapSet.OnlineBeatmapSetID != null) + if (beatmapSet.OnlineID != null) { - var existingSetWithSameOnlineID = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); + var existingSetWithSameOnlineID = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineID == beatmapSet.OnlineID); if (existingSetWithSameOnlineID != null) { Delete(existingSetWithSameOnlineID); // in order to avoid a unique key constraint, immediately remove the online ID from the previous set. - existingSetWithSameOnlineID.OnlineBeatmapSetID = null; + existingSetWithSameOnlineID.OnlineID = null; foreach (var b in existingSetWithSameOnlineID.Beatmaps) b.OnlineID = null; - LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been deleted."); + LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineID}). It has been deleted."); } } } @@ -254,7 +254,7 @@ namespace osu.Game.Beatmaps var importIds = import.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i); // force re-import if we are not in a sane state. - return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); + return existing.OnlineID == import.OnlineID && existingIds.SequenceEqual(importIds); } /// @@ -349,7 +349,7 @@ namespace osu.Game.Beatmaps protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable items) => base.CheckLocalAvailability(model, items) - || (model.OnlineBeatmapSetID != null && items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID)); + || (model.OnlineID != null && items.Any(b => b.OnlineID == model.OnlineID)); protected override BeatmapSetInfo CreateModel(ArchiveReader reader) { @@ -368,7 +368,7 @@ namespace osu.Game.Beatmaps return new BeatmapSetInfo { - OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, + OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineID, Beatmaps = new List(), Metadata = beatmap.Metadata, DateAdded = DateTimeOffset.UtcNow diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index 69b03683dd..7c80d8ad56 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps { beatmapInfo.Status = res.Status; beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapSetOnlineStatus.None; - beatmapInfo.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; + beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID; beatmapInfo.OnlineID = res.OnlineID; if (beatmapInfo.Metadata != null) @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps beatmapInfo.Status = status; beatmapInfo.BeatmapSet.Status = status; - beatmapInfo.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0); + beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); beatmapInfo.OnlineID = reader.GetInt32(1); if (beatmapInfo.Metadata != null) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 0c93c4b9db..f42e6876e3 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -16,12 +16,13 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } - private int? onlineBeatmapSetID; + private int? onlineID; - public int? OnlineBeatmapSetID + [Column("OnlineBeatmapSetID")] + public int? OnlineID { - get => onlineBeatmapSetID; - set => onlineBeatmapSetID = value > 0 ? value : null; + get => onlineID; + set => onlineID = value > 0 ? value : null; } public DateTimeOffset DateAdded { get; set; } @@ -74,8 +75,8 @@ namespace osu.Game.Beatmaps if (ID != 0 && other.ID != 0) return ID == other.ID; - if (OnlineBeatmapSetID.HasValue && other.OnlineBeatmapSetID.HasValue) - return OnlineBeatmapSetID == other.OnlineBeatmapSetID; + if (OnlineID.HasValue && other.OnlineID.HasValue) + return OnlineID == other.OnlineID; if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) return Hash == other.Hash; @@ -85,7 +86,7 @@ namespace osu.Game.Beatmaps #region Implementation of IHasOnlineID - public int OnlineID => OnlineBeatmapSetID ?? -1; + int IHasOnlineID.OnlineID => OnlineID ?? -1; #endregion diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 7a92d03e42..65d050e608 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -267,7 +267,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"BeatmapSetID": - beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = Parsing.ParseInt(pair.Value) }; + beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineID = Parsing.ParseInt(pair.Value) }; break; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index e26b96254f..49853418d6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -134,7 +134,7 @@ namespace osu.Game.Beatmaps.Formats if (!string.IsNullOrEmpty(beatmap.Metadata.Source)) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Tags)) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); if (beatmap.BeatmapInfo.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineID}")); - if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}")); + if (beatmap.BeatmapInfo.BeatmapSet?.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineID}")); } private void handleDifficulty(TextWriter writer) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 91654e2827..d8d2cb8981 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -129,7 +129,7 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.MD5Hash); modelBuilder.Entity().HasIndex(b => b.Hash); - modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapSetID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); modelBuilder.Entity().HasIndex(b => b.DeletePending); modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 592dc60d20..77a8fca1e4 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online return; // Used to interact with manager classes that don't support interface types. Will eventually be replaced. - var beatmapSetInfo = new BeatmapSetInfo { OnlineBeatmapSetID = TrackedItem.OnlineID }; + var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; if (Manager.IsAvailableLocally(beatmapSetInfo)) UpdateState(DownloadState.LocallyAvailable); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bf757a153f..095add399c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -443,7 +443,7 @@ namespace osu.Game BeatmapSetInfo databasedSet = null; if (beatmap.OnlineID > 0) - databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineID); + databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID); if (beatmap is BeatmapSetInfo localBeatmap) databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash); diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 1dcc191c0f..c65d4af2ae 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -241,7 +241,7 @@ namespace osu.Game.Screens.Play if (!automaticDownload.Current.Value) return; - if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID })) + if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineID = beatmapSet.OnlineID })) return; beatmaps.Download(beatmapSet); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index df51287d4a..cc2db6ed31 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Select.Carousel if (!match && criteria.SearchNumber.HasValue) { match = (BeatmapInfo.OnlineID == criteria.SearchNumber.Value) || - (BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID == criteria.SearchNumber.Value); + (BeatmapInfo.BeatmapSet?.OnlineID == criteria.SearchNumber.Value); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 173b804d90..619b1e0fd0 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -214,8 +214,8 @@ namespace osu.Game.Screens.Select.Carousel if (Item.State.Value == CarouselItemState.NotSelected) items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); - if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null) - items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value))); + if (beatmapSet.OnlineID != null && viewDetails != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID.Value))); if (collectionManager != null) { diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index f203e55c27..a9605f1fc3 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -182,7 +182,7 @@ namespace osu.Game.Stores return new RealmBeatmapSet { - OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID ?? -1, + OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineID ?? -1, // Metadata = beatmap.Metadata, DateAdded = DateTimeOffset.UtcNow }; diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index 38c8219813..32a9b0b9d1 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; - BeatmapInfo.BeatmapSet.OnlineBeatmapSetID = Interlocked.Increment(ref onlineSetID); + BeatmapInfo.BeatmapSet.OnlineID = Interlocked.Increment(ref onlineSetID); BeatmapInfo.Length = 75000; BeatmapInfo.OnlineInfo = new APIBeatmap(); BeatmapInfo.OnlineID = Interlocked.Increment(ref onlineBeatmapID); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 83bf130f26..fc7bc324ca 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -203,7 +203,7 @@ namespace osu.Game.Tests.Visual return new APIBeatmapSet { - OnlineID = beatmap.BeatmapSet.OnlineID, + OnlineID = ((IBeatmapSetInfo)beatmap.BeatmapSet).OnlineID, Status = BeatmapSetOnlineStatus.Ranked, Covers = new BeatmapSetOnlineCovers { @@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual new APIBeatmap { OnlineID = ((IBeatmapInfo)beatmap).OnlineID, - OnlineBeatmapSetID = beatmap.BeatmapSet.OnlineID, + OnlineBeatmapSetID = ((IBeatmapSetInfo)beatmap.BeatmapSet).OnlineID, Status = beatmap.Status, Checksum = beatmap.MD5Hash, AuthorID = beatmap.Metadata.Author.OnlineID, From 5e88d59a261575700b7913c8153bc9f005617c5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 19:06:12 +0900 Subject: [PATCH 139/169] Switch `BeatmapInfo.OnlineID` delegation to use property getter for conformity --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 90c5b13bc4..602bb22c40 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -177,7 +177,7 @@ namespace osu.Game.Beatmaps #region Implementation of IHasOnlineID - int IHasOnlineID.OnlineID => onlineID ?? -1; + int IHasOnlineID.OnlineID => OnlineID ?? -1; #endregion From f02b57c37121a77523e4c54aa1a2fa5fc14a3cc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 19:16:22 +0900 Subject: [PATCH 140/169] Limit new `IsZipArchive` method to `MemoryStream` for now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Utils/ZipUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/ZipUtils.cs b/osu.Game/Utils/ZipUtils.cs index 16eb1c7e4e..eb2d2d3b80 100644 --- a/osu.Game/Utils/ZipUtils.cs +++ b/osu.Game/Utils/ZipUtils.cs @@ -9,7 +9,7 @@ namespace osu.Game.Utils { public static class ZipUtils { - public static bool IsZipArchive(Stream stream) + public static bool IsZipArchive(MemoryStream stream) { try { From 338e5a78b89ca0153d4acc7877b1ce1a6906c698 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 19:30:04 +0900 Subject: [PATCH 141/169] Adjust easing of logo to better match the sound I'm sure we can come up with something better, but giving it a bit more speed definitely feels closer to what the sound is portraying. --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index fd54c10d86..ba34df0e59 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -270,9 +270,9 @@ namespace osu.Game.Screens.Play const double duration = 300; - if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.In); + if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint); - logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); + logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint); logo.FadeIn(350); Scheduler.AddDelayed(() => From d2c1bc107218ef2447dd54b10069a11f5c89666e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Nov 2021 19:33:35 +0900 Subject: [PATCH 142/169] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 752eb160ed..f7b7b6fb23 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index df3c9b355a..93fb729f46 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 1852957e87..3a2a3fd65e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 9000d19c9eedca47292e4f283fdd0ab48a8dd9f3 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Fri, 12 Nov 2021 14:58:18 +0100 Subject: [PATCH 143/169] Update difficulty colour spectrum --- osu.Game/Graphics/OsuColour.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 3aa4dbf1d8..d3afb21933 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -52,15 +52,18 @@ namespace osu.Game.Graphics public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(new[] { - (1.5f, Color4Extensions.FromHex("4fc0ff")), + (0.1f, Color4Extensions.FromHex("aaaaaa")), + (0.1f, Color4Extensions.FromHex("4290fb")), + (1.25f, Color4Extensions.FromHex("4fc0ff")), (2.0f, Color4Extensions.FromHex("4fffd5")), (2.5f, Color4Extensions.FromHex("7cff4f")), - (3.25f, Color4Extensions.FromHex("f6f05c")), - (4.5f, Color4Extensions.FromHex("ff8068")), - (6.0f, Color4Extensions.FromHex("ff3c71")), - (7.0f, Color4Extensions.FromHex("6563de")), - (8.0f, Color4Extensions.FromHex("18158e")), - (8.0f, Color4.Black), + (3.3f, Color4Extensions.FromHex("f6f05c")), + (4.2f, Color4Extensions.FromHex("ff8068")), + (4.9f, Color4Extensions.FromHex("ff4e6f")), + (5.8f, Color4Extensions.FromHex("c645b8")), + (6.7f, Color4Extensions.FromHex("6563de")), + (7.7f, Color4Extensions.FromHex("18158e")), + (9.0f, Color4.Black), }, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero)); /// From cb2d1f3f04ae864b330bd2470e5769bb6839536b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 20:28:42 +0100 Subject: [PATCH 144/169] Use horizontally symmetrical padding rather than margin --- osu.Game/Overlays/Settings/SettingsHeader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index 53b3895390..f9ee8df0bd 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -33,9 +33,9 @@ namespace osu.Game.Overlays.Settings { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Margin = new MarginPadding + Padding = new MarginPadding { - Left = SettingsPanel.CONTENT_MARGINS, + Horizontal = SettingsPanel.CONTENT_MARGINS, Top = Toolbar.Toolbar.TOOLTIP_HEIGHT, Bottom = 30 } From 7ba93aac2799be99595ef4d49db3266c60b15d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 21:36:03 +0100 Subject: [PATCH 145/169] Add test coverage for difficulty point piece operation --- ...ceneHitObjectDifficultyPointAdjustments.cs | 123 ++++++++++++++++++ .../Timeline/DifficultyPointPiece.cs | 6 +- 2 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs new file mode 100644 index 0000000000..bbae10e5ff --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -0,0 +1,123 @@ +// 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 Humanizer; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Edit.Timing; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneHitObjectDifficultyPointAdjustments : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("add test objects", () => + { + EditorBeatmap.Add(new Slider + { + StartTime = 0, + Position = (OsuPlayfield.BASE_SIZE - new Vector2(0, 100)) / 2, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(0, 100)) + } + } + }); + + EditorBeatmap.Add(new Slider + { + StartTime = 500, + Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(new Vector2(0, 0)), + new PathControlPoint(new Vector2(100, 0)) + } + }, + DifficultyControlPoint = new DifficultyControlPoint + { + SliderVelocity = 2 + } + }); + }); + } + + [Test] + public void TestSingleSelection() + { + clickDifficultyPiece(0); + velocityPopoverHasSingleValue(1); + + dismissPopover(); + + // select first object to ensure that difficulty pieces for unselected objects + // work independently from selection state. + AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First())); + + clickDifficultyPiece(1); + velocityPopoverHasSingleValue(2); + + setVelocityViaPopover(5); + hitObjectHasVelocity(1, 5); + } + + private void clickDifficultyPiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => + { + var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); + + InputManager.MoveMouseTo(difficultyPiece); + InputManager.Click(MouseButton.Left); + }); + + private void velocityPopoverHasSingleValue(double velocity) => AddUntilStep($"velocity popover has {velocity}", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var slider = popover?.ChildrenOfType>().Single(); + + return slider?.Current.Value == velocity; + }); + + private void dismissPopover() + { + AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); + AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any(popover => popover.IsPresent)); + } + + private void setVelocityViaPopover(double velocity) => AddStep($"set {velocity} via popover", () => + { + var popover = this.ChildrenOfType().Single(); + var slider = popover.ChildrenOfType>().Single(); + slider.Current.Value = velocity; + }); + + private void hitObjectHasVelocity(int objectIndex, double velocity) => AddAssert($"{objectIndex.ToOrdinalWords()} has velocity {velocity}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); + return h.DifficultyControlPoint.SliderVelocity == velocity; + }); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 21457ea273..ae1884d295 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -19,14 +19,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class DifficultyPointPiece : HitObjectPointPiece, IHasPopover { - private readonly HitObject hitObject; + public readonly HitObject HitObject; private readonly BindableNumber speedMultiplier; public DifficultyPointPiece(HitObject hitObject) : base(hitObject.DifficultyControlPoint) { - this.hitObject = hitObject; + HitObject = hitObject; speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy(); } @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } - public Popover GetPopover() => new DifficultyEditPopover(hitObject); + public Popover GetPopover() => new DifficultyEditPopover(HitObject); public class DifficultyEditPopover : OsuPopover { From b8b86cbd2ae50baafae55158e9bb65187a76467c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 21:41:17 +0100 Subject: [PATCH 146/169] Add failing tests for desired multiple selection behaviour --- ...ceneHitObjectDifficultyPointAdjustments.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs index bbae10e5ff..a678582dd6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -85,6 +85,46 @@ namespace osu.Game.Tests.Visual.Editing hitObjectHasVelocity(1, 5); } + [Test] + public void TestMultipleSelectionWithSameSliderVelocity() + { + AddStep("unify slider velocity", () => + { + foreach (var h in EditorBeatmap.HitObjects) + h.DifficultyControlPoint.SliderVelocity = 1.5; + }); + + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickDifficultyPiece(0); + velocityPopoverHasSingleValue(1.5); + + dismissPopover(); + + clickDifficultyPiece(1); + velocityPopoverHasSingleValue(1.5); + + setVelocityViaPopover(5); + hitObjectHasVelocity(0, 5); + hitObjectHasVelocity(1, 5); + } + + [Test] + public void TestMultipleSelectionWithDifferentSliderVelocity() + { + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickDifficultyPiece(0); + velocityPopoverHasIndeterminateValue(); + + dismissPopover(); + + clickDifficultyPiece(1); + velocityPopoverHasIndeterminateValue(); + + setVelocityViaPopover(3); + hitObjectHasVelocity(0, 3); + hitObjectHasVelocity(1, 3); + } + private void clickDifficultyPiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => { var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); @@ -101,6 +141,14 @@ namespace osu.Game.Tests.Visual.Editing return slider?.Current.Value == velocity; }); + private void velocityPopoverHasIndeterminateValue() => AddUntilStep("velocity popover has indeterminate value", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var slider = popover?.ChildrenOfType>().Single(); + + return slider != null && slider.Current.Value == null; + }); + private void dismissPopover() { AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); From e55e2a1697aa7fe18050f61cfc3232f34855c0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 22:54:27 +0100 Subject: [PATCH 147/169] Allow to adjust slider velocity on multiple objects simultaneously --- ...ceneHitObjectDifficultyPointAdjustments.cs | 6 +- .../Timeline/DifficultyPointPiece.cs | 47 ++++--- .../IndeterminateSliderWithTextBoxInput.cs | 115 ++++++++++++++++++ 3 files changed, 151 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs index a678582dd6..4012a672ed 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectDifficultyPointAdjustments.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Editing private void velocityPopoverHasSingleValue(double velocity) => AddUntilStep($"velocity popover has {velocity}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var slider = popover?.ChildrenOfType>().Single(); + var slider = popover?.ChildrenOfType>().Single(); return slider?.Current.Value == velocity; }); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Editing private void velocityPopoverHasIndeterminateValue() => AddUntilStep("velocity popover has indeterminate value", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var slider = popover?.ChildrenOfType>().Single(); + var slider = popover?.ChildrenOfType>().Single(); return slider != null && slider.Current.Value == null; }); @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Editing private void setVelocityViaPopover(double velocity) => AddStep($"set {velocity} via popover", () => { var popover = this.ChildrenOfType().Single(); - var slider = popover.ChildrenOfType>().Single(); + var slider = popover.ChildrenOfType>().Single(); slider.Current.Value = velocity; }); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index ae1884d295..76a8243e05 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -1,9 +1,11 @@ // 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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -49,9 +51,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public class DifficultyEditPopover : OsuPopover { private readonly HitObject hitObject; - private readonly DifficultyControlPoint point; - private SliderWithTextBoxInput sliderVelocitySlider; + private IndeterminateSliderWithTextBoxInput sliderVelocitySlider; [Resolved(canBeNull: true)] private EditorBeatmap beatmap { get; set; } @@ -59,7 +60,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public DifficultyEditPopover(HitObject hitObject) { this.hitObject = hitObject; - point = hitObject.DifficultyControlPoint; } [BackgroundDependencyLoader] @@ -74,9 +74,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AutoSizeAxes = Axes.Y, Children = new Drawable[] { - sliderVelocitySlider = new SliderWithTextBoxInput("Velocity") + sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput("Velocity", new DifficultyControlPoint().SliderVelocityBindable) { - Current = new DifficultyControlPoint().SliderVelocityBindable, KeyboardStep = 0.1f }, new OsuTextFlowContainer @@ -89,17 +88,37 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }; - var selectedPointBindable = point.SliderVelocityBindable; + // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. + // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. + var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); + var relevantControlPoints = relevantObjects.Select(h => h.DifficultyControlPoint).ToArray(); - // there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint). - // generally that level of precision could only be set by externally editing the .osu file, so at the point - // a user is looking to update this within the editor it should be safe to obliterate this additional precision. - double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision; - if (selectedPointBindable.Precision < expectedPrecision) - selectedPointBindable.Precision = expectedPrecision; + // even if there are multiple objects selected, we can still display a value if they all have the same value. + var selectedPointBindable = relevantControlPoints.Select(point => point.SliderVelocity).Distinct().Count() == 1 ? relevantControlPoints.First().SliderVelocityBindable : null; - sliderVelocitySlider.Current = selectedPointBindable; - sliderVelocitySlider.Current.BindValueChanged(_ => beatmap?.Update(hitObject)); + if (selectedPointBindable != null) + { + // there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint). + // generally that level of precision could only be set by externally editing the .osu file, so at the point + // a user is looking to update this within the editor it should be safe to obliterate this additional precision. + sliderVelocitySlider.Current.Value = selectedPointBindable.Value; + } + + sliderVelocitySlider.Current.BindValueChanged(val => + { + if (val.NewValue == null) + return; + + beatmap.BeginChange(); + + foreach (var h in relevantObjects) + { + h.DifficultyControlPoint.SliderVelocity = val.NewValue.Value; + beatmap.Update(h); + } + + beatmap.EndChange(); + }); } } } diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs new file mode 100644 index 0000000000..a5c682e56a --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -0,0 +1,115 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Screens.Edit.Timing +{ + /// + /// Analogous to , but supports scenarios + /// where multiple objects with multiple different property values are selected + /// by providing an "indeterminate state". + /// + public class IndeterminateSliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue + where T : struct, IEquatable, IComparable, IConvertible + { + /// + /// A custom step value for each key press which actuates a change on this control. + /// + public float KeyboardStep + { + get => slider.KeyboardStep; + set => slider.KeyboardStep = value; + } + + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly SettingsSlider slider; + private readonly LabelledTextBox textbox; + + /// + /// Creates an . + /// + /// The label text for the slider and text box. + /// + /// Bindable to use for the slider until a non-null value is set for . + /// In particular, it can be used to control min/max bounds and precision in the case of s. + /// + public IndeterminateSliderWithTextBoxInput(LocalisableString labelText, Bindable indeterminateValue) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + textbox = new LabelledTextBox + { + Label = labelText, + PlaceholderText = "(multiple)" + }, + slider = new SettingsSlider + { + TransferValueOnCommit = true, + RelativeSizeAxes = Axes.X, + Current = indeterminateValue + } + } + }, + }; + + textbox.OnCommit += (t, isNew) => + { + if (!isNew) return; + + try + { + slider.Current.Parse(t.Text); + } + catch + { + // TriggerChange below will restore the previous text value on failure. + } + + // This is run regardless of parsing success as the parsed number may not actually trigger a change + // due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state. + Current.TriggerChange(); + }; + slider.Current.BindValueChanged(val => Current.Value = val.NewValue); + + Current.BindValueChanged(_ => updateState(), true); + } + + private void updateState() + { + if (Current.Value is T nonNullValue) + { + slider.Current.Value = nonNullValue; + textbox.Text = slider.Current.ToString(); + } + else + { + textbox.Text = null; + } + } + } +} From d567d2be973cd860122454bfbb64b7150ef0a8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 23:07:24 +0100 Subject: [PATCH 148/169] Fix multiple issues with textbox content display - Sometimes would display too many decimal digits due to floating point representation errors. - Placeholder would also look wrong if text was removed during a multiple (but determinate) selection. --- .../Graphics/UserInterface/OsuSliderBar.cs | 21 ++----------------- .../IndeterminateSliderWithTextBoxInput.cs | 10 +++++++-- osu.Game/Utils/FormatUtils.cs | 18 ++++++++++++++++ 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index d4310dc901..333ae4f832 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -19,6 +19,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Overlays; +using osu.Game.Utils; namespace osu.Game.Graphics.UserInterface { @@ -219,7 +220,7 @@ namespace osu.Game.Graphics.UserInterface decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); // Find the number of significant digits (we could have less than 5 after normalize()) - int significantDigits = findPrecision(decimalPrecision); + int significantDigits = FormatUtils.FindPrecision(decimalPrecision); TooltipText = floatValue.ToString($"N{significantDigits}"); } @@ -248,23 +249,5 @@ namespace osu.Game.Graphics.UserInterface /// The normalised decimal. private decimal normalise(decimal d, int sd) => decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); - - /// - /// Finds the number of digits after the decimal. - /// - /// The value to find the number of decimal digits for. - /// The number decimal digits. - private int findPrecision(decimal d) - { - int precision = 0; - - while (d != Math.Round(d)) - { - d *= 10; - precision++; - } - - return precision; - } } } diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index a5c682e56a..17f82f4978 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -9,6 +10,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; +using osu.Game.Utils; namespace osu.Game.Screens.Edit.Timing { @@ -65,7 +67,6 @@ namespace osu.Game.Screens.Edit.Timing textbox = new LabelledTextBox { Label = labelText, - PlaceholderText = "(multiple)" }, slider = new SettingsSlider { @@ -104,11 +105,16 @@ namespace osu.Game.Screens.Edit.Timing if (Current.Value is T nonNullValue) { slider.Current.Value = nonNullValue; - textbox.Text = slider.Current.ToString(); + + // use the value from the slider to ensure that any precision/min/max set on it via the initial indeterminate value have been applied correctly. + decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); + textbox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); + textbox.PlaceholderText = string.Empty; } else { textbox.Text = null; + textbox.PlaceholderText = "(multiple)"; } } } diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index d14dbb49f3..799dc75ca9 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -31,5 +31,23 @@ namespace osu.Game.Utils /// /// The rank/position to be formatted. public static string FormatRank(this int rank) => rank.ToMetric(decimals: rank < 100_000 ? 1 : 0); + + /// + /// Finds the number of digits after the decimal. + /// + /// The value to find the number of decimal digits for. + /// The number decimal digits. + public static int FindPrecision(decimal d) + { + int precision = 0; + + while (d != Math.Round(d)) + { + d *= 10; + precision++; + } + + return precision; + } } } From e1c28ddd7617d15f466b6c7546b1e1ca353c85d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 23:16:45 +0100 Subject: [PATCH 149/169] Adjust difficulty point popover content spacing --- .../Edit/Compose/Components/Timeline/DifficultyPointPiece.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 76a8243e05..b230bab0c2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Timing; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -72,6 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Width = 200, Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 15), Children = new Drawable[] { sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput("Velocity", new DifficultyControlPoint().SliderVelocityBindable) From a5ba3bd012c95a6ab39ffcf3274a0ac74590efa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 12:07:38 +0100 Subject: [PATCH 150/169] Move gameplay test pieces to own namespace --- osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs | 1 + osu.Game/Screens/Edit/Editor.cs | 1 + osu.Game/Screens/Edit/{ => GameplayTest}/EditorPlayer.cs | 2 +- .../Edit/{ => GameplayTest}/SaveBeforeGameplayTestDialog.cs | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) rename osu.Game/Screens/Edit/{ => GameplayTest}/EditorPlayer.cs (96%) rename osu.Game/Screens/Edit/{ => GameplayTest}/SaveBeforeGameplayTestDialog.cs (95%) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index db42667033..e311c7a7ec 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 738f607cc8..0c54e39a35 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -31,6 +31,7 @@ using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Design; +using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Verify; diff --git a/osu.Game/Screens/Edit/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs similarity index 96% rename from osu.Game/Screens/Edit/EditorPlayer.cs rename to osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index b2fab3fefc..9856ad62bb 100644 --- a/osu.Game/Screens/Edit/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -6,7 +6,7 @@ using osu.Framework.Screens; using osu.Game.Overlays; using osu.Game.Screens.Play; -namespace osu.Game.Screens.Edit +namespace osu.Game.Screens.Edit.GameplayTest { public class EditorPlayer : Player { diff --git a/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs b/osu.Game/Screens/Edit/GameplayTest/SaveBeforeGameplayTestDialog.cs similarity index 95% rename from osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs rename to osu.Game/Screens/Edit/GameplayTest/SaveBeforeGameplayTestDialog.cs index 7c03664c66..9334c74706 100644 --- a/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs +++ b/osu.Game/Screens/Edit/GameplayTest/SaveBeforeGameplayTestDialog.cs @@ -5,7 +5,7 @@ using System; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; -namespace osu.Game.Screens.Edit +namespace osu.Game.Screens.Edit.GameplayTest { public class SaveBeforeGameplayTestDialog : PopupDialog { From b47c0b63f4305fe3de7bcaf0d142825ddf3b3ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 12:30:40 +0100 Subject: [PATCH 151/169] Tweak loader transition when testing gameplay in editor --- osu.Game/Screens/Edit/Editor.cs | 2 +- .../Edit/GameplayTest/EditorPlayerLoader.cs | 44 +++++++++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 26 ++++++----- 3 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0c54e39a35..2c758eaf41 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -798,7 +798,7 @@ namespace osu.Game.Screens.Edit pushEditorPlayer(); } - void pushEditorPlayer() => this.Push(new PlayerLoader(() => new EditorPlayer())); + void pushEditorPlayer() => this.Push(new EditorPlayerLoader()); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs new file mode 100644 index 0000000000..610fff70f2 --- /dev/null +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs @@ -0,0 +1,44 @@ +// 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.Screens; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; + +namespace osu.Game.Screens.Edit.GameplayTest +{ + public class EditorPlayerLoader : PlayerLoader + { + [Resolved] + private OsuLogo osuLogo { get; set; } + + public EditorPlayerLoader() + : base(() => new EditorPlayer()) + { + } + + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + + MetadataInfo.FinishTransforms(true); + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + // call base with resuming forcefully set to true to reduce logo movements. + base.LogoArriving(logo, true); + logo.FinishTransforms(true, nameof(Scale)); + } + + protected override void ContentOut() + { + base.ContentOut(); + osuLogo.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint); + } + + protected override double PlayerPushDelay => 0; + } +} diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ba34df0e59..57db411571 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -36,7 +36,9 @@ namespace osu.Game.Screens.Play { protected const float BACKGROUND_BLUR = 15; - private const double content_out_duration = 300; + protected const double CONTENT_OUT_DURATION = 300; + + protected virtual double PlayerPushDelay => 1800; public override bool HideOverlaysOnEnter => hideOverlays; @@ -213,7 +215,7 @@ namespace osu.Game.Screens.Play // after an initial delay, start the debounced load check. // this will continue to execute even after resuming back on restart. - Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0)); + Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + PlayerPushDelay, 0)); showMuteWarningIfNeeded(); showBatteryWarningIfNeeded(); @@ -248,13 +250,13 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { cancelLoad(); - contentOut(); + ContentOut(); // If the load sequence was interrupted, the epilepsy warning may already be displayed (or in the process of being displayed). epilepsyWarning?.Hide(); // Ensure the screen doesn't expire until all the outwards fade operations have completed. - this.Delay(content_out_duration).FadeOut(); + this.Delay(CONTENT_OUT_DURATION).FadeOut(); ApplyToBackground(b => b.IgnoreUserSettings.Value = true); @@ -361,15 +363,15 @@ namespace osu.Game.Screens.Play ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); } - private void contentOut() + protected virtual void ContentOut() { // Ensure the logo is no longer tracking before we scale the content content.StopTracking(); - content.ScaleTo(0.7f, content_out_duration * 2, Easing.OutQuint); - content.FadeOut(content_out_duration, Easing.OutQuint); - lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, content_out_duration); - highPassFilter.CutoffTo(0, content_out_duration); + content.ScaleTo(0.7f, CONTENT_OUT_DURATION * 2, Easing.OutQuint); + content.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint); + lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, CONTENT_OUT_DURATION); + highPassFilter.CutoffTo(0, CONTENT_OUT_DURATION); } private void pushWhenLoaded() @@ -394,9 +396,9 @@ namespace osu.Game.Screens.Play // ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared). var consumedPlayer = consumePlayer(); - contentOut(); + ContentOut(); - TransformSequence pushSequence = this.Delay(content_out_duration); + TransformSequence pushSequence = this.Delay(CONTENT_OUT_DURATION); // only show if the warning was created (i.e. the beatmap needs it) // and this is not a restart of the map (the warning expires after first load). @@ -418,7 +420,7 @@ namespace osu.Game.Screens.Play else { // This goes hand-in-hand with the restoration of low pass filter in contentOut(). - this.TransformBindableTo(volumeAdjustment, 0, content_out_duration, Easing.OutCubic); + this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic); } pushSequence.Schedule(() => From eb8c5292d5db823f1f99947ca936d3e8d3e722f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 13:50:57 +0100 Subject: [PATCH 152/169] Ensure editor background is dimmed after return from gameplay test --- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 7 +++++++ osu.Game/Screens/Edit/Editor.cs | 13 +++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index e311c7a7ec..f52a764708 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -10,10 +10,12 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Tests.Beatmaps.IO; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing @@ -59,6 +61,11 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); AddStep("exit player", () => editorPlayer.Exit()); AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + AddUntilStep("background has correct params", () => + { + var background = this.ChildrenOfType().Single(); + return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; + }); } [Test] diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2c758eaf41..d97838e86d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -487,7 +487,18 @@ namespace osu.Game.Screens.Edit public override void OnEntering(IScreen last) { base.OnEntering(last); + dimBackground(); + resetTrack(true); + } + public override void OnResuming(IScreen last) + { + base.OnResuming(last); + dimBackground(); + } + + private void dimBackground() + { ApplyToBackground(b => { // todo: temporary. we want to be applying dim using the UserDimContainer eventually. @@ -496,8 +507,6 @@ namespace osu.Game.Screens.Edit b.IgnoreUserSettings.Value = true; b.BlurAmount.Value = 0; }); - - resetTrack(true); } public override bool OnExiting(IScreen next) From 5f2a789a6d58b564d79b03aa48475a3c6b686450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 14:01:00 +0100 Subject: [PATCH 153/169] Ensure editor clock is stopped before testing gameplay --- .../Editing/TestSceneEditorTestGameplay.cs | 31 +++++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 4 +-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index f52a764708..1d572772eb 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -68,6 +68,37 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestGameplayTestWhenTrackRunning() + { + AddStep("start track", () => EditorClock.Start()); + AddAssert("sample playback enabled", () => !Editor.SamplePlaybackDisabled.Value); + + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + AddAssert("editor track stopped", () => !EditorClock.IsRunning); + AddAssert("sample playback disabled", () => Editor.SamplePlaybackDisabled.Value); + + AddStep("exit player", () => editorPlayer.Exit()); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + AddUntilStep("background has correct params", () => + { + var background = this.ChildrenOfType().Single(); + return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; + }); + + AddStep("start track", () => EditorClock.Start()); + AddAssert("sample playback re-enabled", () => !Editor.SamplePlaybackDisabled.Value); + } + [Test] public void TestCancelGameplayTestWithUnsavedChanges() { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d97838e86d..ab24391bb4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -545,9 +545,9 @@ namespace osu.Game.Screens.Edit public override void OnSuspending(IScreen next) { - refetchBeatmap(); - base.OnSuspending(next); + clock.Stop(); + refetchBeatmap(); } private void refetchBeatmap() From 9800cd490384bf41bebb1fe0d105e3a7c7ae4f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 16:58:24 +0100 Subject: [PATCH 154/169] Add test coverage for sample control point piece operation --- ...estSceneHitObjectSamplePointAdjustments.cs | 139 ++++++++++++++++++ .../Components/Timeline/SamplePointPiece.cs | 6 +- 2 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs new file mode 100644 index 0000000000..24048e6052 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -0,0 +1,139 @@ +// 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 Humanizer; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Edit.Timing; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneHitObjectSamplePointAdjustments : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("add test objects", () => + { + EditorBeatmap.Add(new HitCircle + { + StartTime = 0, + Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2, + SampleControlPoint = new SampleControlPoint + { + SampleBank = "normal", + SampleVolume = 80 + } + }); + + EditorBeatmap.Add(new HitCircle() + { + StartTime = 500, + Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2, + SampleControlPoint = new SampleControlPoint + { + SampleBank = "soft", + SampleVolume = 60 + } + }); + }); + } + + [Test] + public void TestSingleSelection() + { + clickSamplePiece(0); + samplePopoverHasSingleBank("normal"); + samplePopoverHasSingleVolume(80); + + dismissPopover(); + + // select first object to ensure that sample pieces for unselected objects + // work independently from selection state. + AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First())); + + clickSamplePiece(1); + samplePopoverHasSingleBank("soft"); + samplePopoverHasSingleVolume(60); + + setVolumeViaPopover(90); + hitObjectHasSampleVolume(1, 90); + + setBankViaPopover("drum"); + hitObjectHasSampleBank(1, "drum"); + } + + private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => + { + var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); + + InputManager.MoveMouseTo(difficultyPiece); + InputManager.Click(MouseButton.Left); + }); + + private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var slider = popover?.ChildrenOfType>().Single(); + + return slider?.Current.Value == volume; + }); + + private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var textBox = popover?.ChildrenOfType().First(); + + return textBox?.Current.Value == bank; + }); + + private void dismissPopover() + { + AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); + AddUntilStep("wait for dismiss", () => !this.ChildrenOfType().Any(popover => popover.IsPresent)); + } + + private void setVolumeViaPopover(int volume) => AddStep($"set volume {volume} via popover", () => + { + var popover = this.ChildrenOfType().Single(); + var slider = popover.ChildrenOfType>().Single(); + slider.Current.Value = volume; + }); + + private void hitObjectHasSampleVolume(int objectIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} has volume {volume}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); + return h.SampleControlPoint.SampleVolume == volume; + }); + + private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () => + { + var popover = this.ChildrenOfType().Single(); + var textBox = popover.ChildrenOfType().First(); + textBox.Current.Value = bank; + }); + + private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex); + return h.SampleControlPoint.SampleBank == bank; + }); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 6a26f69e41..6250a9ccb8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class SamplePointPiece : HitObjectPointPiece, IHasPopover { - private readonly HitObject hitObject; + public readonly HitObject HitObject; private readonly Bindable bank; private readonly BindableNumber volume; @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public SamplePointPiece(HitObject hitObject) : base(hitObject.SampleControlPoint) { - this.hitObject = hitObject; + HitObject = hitObject; volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy(); bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy(); } @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Label.Text = $"{bank.Value} {volume.Value}"; } - public Popover GetPopover() => new SampleEditPopover(hitObject); + public Popover GetPopover() => new SampleEditPopover(HitObject); public class SampleEditPopover : OsuPopover { From 6ba154129bdae1037e4a3ae305020b0193e949b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 17:02:55 +0100 Subject: [PATCH 155/169] Add failing test coverage for setting sample volume on multiple objects --- ...estSceneHitObjectSamplePointAdjustments.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 24048e6052..0c4056e90c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -80,6 +80,46 @@ namespace osu.Game.Tests.Visual.Editing hitObjectHasSampleBank(1, "drum"); } + [Test] + public void TestMultipleSelectionWithSameSampleVolume() + { + AddStep("unify sample volume", () => + { + foreach (var h in EditorBeatmap.HitObjects) + h.SampleControlPoint.SampleVolume = 50; + }); + + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickSamplePiece(0); + samplePopoverHasSingleVolume(50); + + dismissPopover(); + + clickSamplePiece(1); + samplePopoverHasSingleVolume(50); + + setVolumeViaPopover(75); + hitObjectHasSampleVolume(0, 75); + hitObjectHasSampleVolume(1, 75); + } + + [Test] + public void TestMultipleSelectionWithDifferentSampleVolume() + { + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickSamplePiece(0); + samplePopoverHasIndeterminateVolume(); + + dismissPopover(); + + clickSamplePiece(1); + samplePopoverHasIndeterminateVolume(); + + setVolumeViaPopover(30); + hitObjectHasSampleVolume(0, 30); + hitObjectHasSampleVolume(1, 30); + } + private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => { var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); @@ -96,6 +136,14 @@ namespace osu.Game.Tests.Visual.Editing return slider?.Current.Value == volume; }); + private void samplePopoverHasIndeterminateVolume() => AddUntilStep($"sample popover has indeterminate volume", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var slider = popover?.ChildrenOfType>().Single(); + + return slider != null && slider.Current.Value == null; + }); + private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); From 9cf45e418ab78da6e12dd40665777a285cd88269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 17:07:19 +0100 Subject: [PATCH 156/169] Add failing test coverage for setting sample bank on multiple objects --- ...estSceneHitObjectSamplePointAdjustments.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 0c4056e90c..5b2b6fc780 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -120,6 +120,46 @@ namespace osu.Game.Tests.Visual.Editing hitObjectHasSampleVolume(1, 30); } + [Test] + public void TestMultipleSelectionWithSameSampleBank() + { + AddStep("unify sample bank", () => + { + foreach (var h in EditorBeatmap.HitObjects) + h.SampleControlPoint.SampleBank = "soft"; + }); + + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickSamplePiece(0); + samplePopoverHasSingleBank("soft"); + + dismissPopover(); + + clickSamplePiece(1); + samplePopoverHasSingleBank("soft"); + + setBankViaPopover("drum"); + hitObjectHasSampleBank(0, "drum"); + hitObjectHasSampleBank(1, "drum"); + } + + [Test] + public void TestMultipleSelectionWithDifferentSampleBank() + { + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + clickSamplePiece(0); + samplePopoverHasIndeterminateBank(); + + dismissPopover(); + + clickSamplePiece(1); + samplePopoverHasIndeterminateBank(); + + setBankViaPopover("normal"); + hitObjectHasSampleBank(0, "normal"); + hitObjectHasSampleBank(1, "normal"); + } + private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => { var difficultyPiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); @@ -152,6 +192,14 @@ namespace osu.Game.Tests.Visual.Editing return textBox?.Current.Value == bank; }); + private void samplePopoverHasIndeterminateBank() => AddUntilStep($"sample popover has indeterminate bank", () => + { + var popover = this.ChildrenOfType().SingleOrDefault(); + var textBox = popover?.ChildrenOfType().First(); + + return textBox != null && textBox.Current.Value == null; + }); + private void dismissPopover() { AddStep("dismiss popover", () => InputManager.Key(Key.Escape)); From 3fee6b0938261e62654a1b1e8ab677d68653e524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 17:21:48 +0100 Subject: [PATCH 157/169] Add support for setting sample bank & volume for multiple objects at once --- ...estSceneHitObjectSamplePointAdjustments.cs | 10 +-- .../Components/Timeline/SamplePointPiece.cs | 71 +++++++++++++++---- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 5b2b6fc780..f30c56d53c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -171,15 +171,15 @@ namespace osu.Game.Tests.Visual.Editing private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var slider = popover?.ChildrenOfType>().Single(); + var slider = popover?.ChildrenOfType>().Single(); return slider?.Current.Value == volume; }); - private void samplePopoverHasIndeterminateVolume() => AddUntilStep($"sample popover has indeterminate volume", () => + private void samplePopoverHasIndeterminateVolume() => AddUntilStep("sample popover has indeterminate volume", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var slider = popover?.ChildrenOfType>().Single(); + var slider = popover?.ChildrenOfType>().Single(); return slider != null && slider.Current.Value == null; }); @@ -197,7 +197,7 @@ namespace osu.Game.Tests.Visual.Editing var popover = this.ChildrenOfType().SingleOrDefault(); var textBox = popover?.ChildrenOfType().First(); - return textBox != null && textBox.Current.Value == null; + return textBox != null && string.IsNullOrEmpty(textBox.Current.Value); }); private void dismissPopover() @@ -209,7 +209,7 @@ namespace osu.Game.Tests.Visual.Editing private void setVolumeViaPopover(int volume) => AddStep($"set volume {volume} via popover", () => { var popover = this.ChildrenOfType().Single(); - var slider = popover.ChildrenOfType>().Single(); + var slider = popover.ChildrenOfType>().Single(); slider.Current.Value = volume; }); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 6250a9ccb8..f0b11ce96e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -1,9 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -55,18 +60,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public class SampleEditPopover : OsuPopover { private readonly HitObject hitObject; - private readonly SampleControlPoint point; - private LabelledTextBox bank; - private SliderWithTextBoxInput volume; + private LabelledTextBox bank = null!; + private IndeterminateSliderWithTextBoxInput volume = null!; [Resolved(canBeNull: true)] - private EditorBeatmap beatmap { get; set; } + private EditorBeatmap beatmap { get; set; } = null!; public SampleEditPopover(HitObject hitObject) { this.hitObject = hitObject; - point = hitObject.SampleControlPoint; } [BackgroundDependencyLoader] @@ -85,19 +88,61 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Label = "Bank Name", }, - volume = new SliderWithTextBoxInput("Volume") - { - Current = new SampleControlPoint().SampleVolumeBindable, - } + volume = new IndeterminateSliderWithTextBoxInput("Volume", new SampleControlPoint().SampleVolumeBindable) } } }; - bank.Current = point.SampleBankBindable; - bank.Current.BindValueChanged(_ => beatmap.Update(hitObject)); + // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. + // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. + var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); + var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray(); - volume.Current = point.SampleVolumeBindable; - volume.Current.BindValueChanged(_ => beatmap.Update(hitObject)); + // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. + string? commonBank = relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; + + if (!string.IsNullOrEmpty(commonBank)) + bank.Current.Value = commonBank; + + int? commonVolume = relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null; + + if (commonVolume != null) + volume.Current.Value = commonVolume.Value; + + bank.Current.BindValueChanged(val => updateBankFor(relevantObjects, val.NewValue)); + volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); + } + + private void updateBankFor(IEnumerable objects, string? newBank) + { + if (string.IsNullOrEmpty(newBank)) + return; + + beatmap.BeginChange(); + + foreach (var h in objects) + { + h.SampleControlPoint.SampleBank = newBank; + beatmap.Update(h); + } + + beatmap.EndChange(); + } + + private void updateVolumeFor(IEnumerable objects, int? newVolume) + { + if (newVolume == null) + return; + + beatmap.BeginChange(); + + foreach (var h in objects) + { + h.SampleControlPoint.SampleVolume = newVolume.Value; + beatmap.Update(h); + } + + beatmap.EndChange(); } } } From 76baf08140b4ae9971a44c9443f5b780a3d7ecef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 17:43:33 +0100 Subject: [PATCH 158/169] Expand test coverage with desired bank text box placeholder behaviour --- ...estSceneHitObjectSamplePointAdjustments.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index f30c56d53c..460b608166 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Editing } }); - EditorBeatmap.Add(new HitCircle() + EditorBeatmap.Add(new HitCircle { StartTime = 500, Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2, @@ -138,9 +139,15 @@ namespace osu.Game.Tests.Visual.Editing clickSamplePiece(1); samplePopoverHasSingleBank("soft"); + setBankViaPopover(string.Empty); + hitObjectHasSampleBank(0, "soft"); + hitObjectHasSampleBank(1, "soft"); + samplePopoverHasSingleBank("soft"); + setBankViaPopover("drum"); hitObjectHasSampleBank(0, "drum"); hitObjectHasSampleBank(1, "drum"); + samplePopoverHasSingleBank("drum"); } [Test] @@ -155,9 +162,15 @@ namespace osu.Game.Tests.Visual.Editing clickSamplePiece(1); samplePopoverHasIndeterminateBank(); + setBankViaPopover(string.Empty); + hitObjectHasSampleBank(0, "normal"); + hitObjectHasSampleBank(1, "soft"); + samplePopoverHasIndeterminateBank(); + setBankViaPopover("normal"); hitObjectHasSampleBank(0, "normal"); hitObjectHasSampleBank(1, "normal"); + samplePopoverHasSingleBank("normal"); } private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () => @@ -187,17 +200,17 @@ namespace osu.Game.Tests.Visual.Editing private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var textBox = popover?.ChildrenOfType().First(); + var textBox = popover?.ChildrenOfType().First(); - return textBox?.Current.Value == bank; + return textBox?.Current.Value == bank && string.IsNullOrEmpty(textBox?.PlaceholderText.ToString()); }); - private void samplePopoverHasIndeterminateBank() => AddUntilStep($"sample popover has indeterminate bank", () => + private void samplePopoverHasIndeterminateBank() => AddUntilStep("sample popover has indeterminate bank", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var textBox = popover?.ChildrenOfType().First(); + var textBox = popover?.ChildrenOfType().First(); - return textBox != null && string.IsNullOrEmpty(textBox.Current.Value); + return textBox != null && string.IsNullOrEmpty(textBox.Current.Value) && !string.IsNullOrEmpty(textBox.PlaceholderText.ToString()); }); private void dismissPopover() From 73ca1d39a2fbaeef88ad13ebc7904027eece8d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 18:06:32 +0100 Subject: [PATCH 159/169] Improve sample bank text box UX in case of multiple selection --- ...estSceneHitObjectSamplePointAdjustments.cs | 4 +++ .../Components/Timeline/SamplePointPiece.cs | 26 +++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 460b608166..dca30a6fc0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -237,6 +237,10 @@ namespace osu.Game.Tests.Visual.Editing var popover = this.ChildrenOfType().Single(); var textBox = popover.ChildrenOfType().First(); textBox.Current.Value = bank; + // force a commit via keyboard. + // this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit. + InputManager.ChangeFocus(textBox); + InputManager.Key(Key.Enter); }); private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () => diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index f0b11ce96e..fbbbb153b9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -99,20 +99,30 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. - string? commonBank = relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; - + string? commonBank = getCommonBank(relevantControlPoints); if (!string.IsNullOrEmpty(commonBank)) bank.Current.Value = commonBank; - int? commonVolume = relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null; - + int? commonVolume = getCommonVolume(relevantControlPoints); if (commonVolume != null) volume.Current.Value = commonVolume.Value; - bank.Current.BindValueChanged(val => updateBankFor(relevantObjects, val.NewValue)); + updateBankPlaceholderText(relevantObjects); + bank.Current.BindValueChanged(val => + { + updateBankFor(relevantObjects, val.NewValue); + updateBankPlaceholderText(relevantObjects); + }); + // on commit, ensure that the value is correct by sourcing it from the objects' control points again. + // this ensures that committing empty text causes a revert to the previous value. + bank.OnCommit += (_, __) => bank.Current.Value = getCommonBank(relevantControlPoints); + volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); } + private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; + private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null; + private void updateBankFor(IEnumerable objects, string? newBank) { if (string.IsNullOrEmpty(newBank)) @@ -129,6 +139,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatmap.EndChange(); } + private void updateBankPlaceholderText(IEnumerable objects) + { + string? commonBank = getCommonBank(objects.Select(h => h.SampleControlPoint).ToArray()); + bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : null; + } + private void updateVolumeFor(IEnumerable objects, int? newVolume) { if (newVolume == null) From 9a19a516f99cd62b4b2dc5d5baa76e982ca96bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Nov 2021 18:09:40 +0100 Subject: [PATCH 160/169] Adjust spacings on sample point piece popover --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 ++ .../Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index fbbbb153b9..2cbfe88519 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Timing; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -82,6 +83,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Width = 200, Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 10), Children = new Drawable[] { bank = new LabelledTextBox diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 17f82f4978..14b8c4c9de 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; using osu.Game.Utils; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -62,6 +63,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), Children = new Drawable[] { textbox = new LabelledTextBox From ca239ca40a2913103958552d911342bb2abef98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 12:41:16 +0100 Subject: [PATCH 161/169] Add failing test case for sharing clock state with gameplay test --- .../Editing/TestSceneEditorTestGameplay.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 1d572772eb..160af47a6d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -14,6 +14,7 @@ using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.GameplayTest; +using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osuTK.Graphics; using osuTK.Input; @@ -150,6 +151,35 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning); } + [Test] + public void TestSharedClockState() + { + AddStep("seek to 00:01:00", () => EditorClock.Seek(60_000)); + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + + GameplayClockContainer gameplayClockContainer = null; + AddStep("fetch gameplay clock", () => gameplayClockContainer = editorPlayer.ChildrenOfType().First()); + AddUntilStep("gameplay clock running", () => gameplayClockContainer.IsRunning); + AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000); + + double timeAtPlayerExit = 0; + AddWaitStep("wait some", 5); + AddStep("store time before exit", () => timeAtPlayerExit = gameplayClockContainer.CurrentTime); + + AddStep("exit player", () => editorPlayer.Exit()); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + AddAssert("time is past player exit", () => EditorClock.CurrentTime >= timeAtPlayerExit); + } + public override void TearDownSteps() { base.TearDownSteps(); From d2ddc25ab3a7457bf2496e5a5216f29ba31b4c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 12:50:38 +0100 Subject: [PATCH 162/169] Propagate clock state from editor to gameplay test --- osu.Game/Screens/Edit/Editor.cs | 21 +++++++++++++------ .../Screens/Edit/GameplayTest/EditorPlayer.cs | 15 +++++++++---- .../Edit/GameplayTest/EditorPlayerLoader.cs | 4 ++-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ab24391bb4..ff9a19434b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -325,6 +325,19 @@ namespace osu.Game.Screens.Edit /// public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track); + /// + /// Creates an instance representing the current state of the editor. + /// + /// + /// The next beatmap to be shown, in the case of difficulty switch. + /// indicates that the beatmap will not be changing. + /// + private EditorState getState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState + { + Time = clock.CurrentTimeAccurate, + ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty + }; + /// /// Restore the editor to a provided state. /// @@ -780,11 +793,7 @@ namespace osu.Game.Screens.Edit return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); } - protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState - { - Time = clock.CurrentTimeAccurate, - ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty - }); + protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, getState(nextBeatmap)); private void cancelExit() { @@ -807,7 +816,7 @@ namespace osu.Game.Screens.Edit pushEditorPlayer(); } - void pushEditorPlayer() => this.Push(new EditorPlayerLoader()); + void pushEditorPlayer() => this.Push(new EditorPlayerLoader(getState())); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 9856ad62bb..266c5e81d4 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Screens.Play; @@ -10,14 +11,20 @@ namespace osu.Game.Screens.Edit.GameplayTest { public class EditorPlayer : Player { - public EditorPlayer() - : base(new PlayerConfiguration { ShowResults = false }) - { - } + private readonly EditorState editorState; [Resolved] private MusicController musicController { get; set; } + public EditorPlayer(EditorState editorState) + : base(new PlayerConfiguration { ShowResults = false }) + { + this.editorState = editorState; + } + + protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) + => new MasterGameplayClockContainer(beatmap, editorState.Time, true); + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs index 610fff70f2..5c2ab04fd7 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs @@ -14,8 +14,8 @@ namespace osu.Game.Screens.Edit.GameplayTest [Resolved] private OsuLogo osuLogo { get; set; } - public EditorPlayerLoader() - : base(() => new EditorPlayer()) + public EditorPlayerLoader(EditorState editorState) + : base(() => new EditorPlayer(editorState)) { } From 2562412125e974efc2c89a4b4b31daa2070aa589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 13:12:25 +0100 Subject: [PATCH 163/169] Propagate clock state from gameplay test back to editor --- osu.Game/Screens/Edit/Editor.cs | 6 +++--- osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 9 +++++++-- osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ff9a19434b..2a7e2c9cef 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -332,7 +332,7 @@ namespace osu.Game.Screens.Edit /// The next beatmap to be shown, in the case of difficulty switch. /// indicates that the beatmap will not be changing. /// - private EditorState getState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState + public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState { Time = clock.CurrentTimeAccurate, ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty @@ -793,7 +793,7 @@ namespace osu.Game.Screens.Edit return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); } - protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, getState(nextBeatmap)); + protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap)); private void cancelExit() { @@ -816,7 +816,7 @@ namespace osu.Game.Screens.Edit pushEditorPlayer(); } - void pushEditorPlayer() => this.Push(new EditorPlayerLoader(getState())); + void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this)); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 266c5e81d4..479dc2e6b3 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -11,15 +11,17 @@ namespace osu.Game.Screens.Edit.GameplayTest { public class EditorPlayer : Player { + private readonly Editor editor; private readonly EditorState editorState; [Resolved] private MusicController musicController { get; set; } - public EditorPlayer(EditorState editorState) + public EditorPlayer(Editor editor) : base(new PlayerConfiguration { ShowResults = false }) { - this.editorState = editorState; + this.editor = editor; + editorState = editor.GetState(); } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) @@ -45,6 +47,9 @@ namespace osu.Game.Screens.Edit.GameplayTest public override bool OnExiting(IScreen next) { musicController.Stop(); + + editorState.Time = GameplayClockContainer.CurrentTime; + editor.RestoreState(editorState); return base.OnExiting(next); } } diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs index 5c2ab04fd7..addc79ba61 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs @@ -14,8 +14,8 @@ namespace osu.Game.Screens.Edit.GameplayTest [Resolved] private OsuLogo osuLogo { get; set; } - public EditorPlayerLoader(EditorState editorState) - : base(() => new EditorPlayer(editorState)) + public EditorPlayerLoader(Editor editor) + : base(() => new EditorPlayer(editor)) { } From 6b4b6de5545f16762a8a63fdeb011ed6e95ee093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Nov 2021 13:51:02 +0100 Subject: [PATCH 164/169] Fix test gameplay starting in a hidden state --- osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 479dc2e6b3..f49603c754 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -44,6 +44,16 @@ namespace osu.Game.Screens.Edit.GameplayTest protected override bool CheckModsAllowFailure() => false; // never fail. + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + + // finish alpha transforms on entering to avoid gameplay starting in a half-hidden state. + // the finish calls are purposefully not propagated to children to avoid messing up their state. + FinishTransforms(); + GameplayClockContainer.FinishTransforms(false, nameof(Alpha)); + } + public override bool OnExiting(IScreen next) { musicController.Stop(); From 5be22916bb80ca652099369e6a69b2cee955fca5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Nov 2021 14:26:18 +0900 Subject: [PATCH 165/169] Add test coverage against frequency ramp increasing between fail animation and fail screen --- .../Visual/Gameplay/TestSceneFailAnimation.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index 36fc6812bd..7167d3120a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -31,12 +31,18 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("wait for fail", () => Player.HasFailed); AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible); + + // The pause screen and fail animation both ramp frequency. + // This tests to ensure that it doesn't reset during that handoff. + AddAssert("frequency only ever decreased", () => !((FailPlayer)Player).FrequencyIncreased); } private class FailPlayer : TestPlayer { public new FailOverlay FailOverlay => base.FailOverlay; + public bool FrequencyIncreased { get; private set; } + public FailPlayer() : base(false, false) { @@ -47,6 +53,19 @@ namespace osu.Game.Tests.Visual.Gameplay base.LoadComplete(); HealthProcessor.FailConditions += (_, __) => true; } + + private double lastFrequency = double.MaxValue; + + protected override void Update() + { + base.Update(); + + double freq = Beatmap.Value.Track.AggregateFrequency.Value; + + FrequencyIncreased |= freq > lastFrequency; + + lastFrequency = freq; + } } } } From a4aa0087934c54b7dec27a833a83da6c7f692233 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Nov 2021 14:13:12 +0900 Subject: [PATCH 166/169] Fix frequency ramping a second time incorrectly after fail --- osu.Game/Screens/Play/FailAnimation.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index f3676baf80..193e1e4129 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -107,7 +107,8 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ => { - RemoveFilters(); + // Don't reset frequency as the pause screen may appear post transform, causing a second frequency sweep. + RemoveFilters(false); OnComplete?.Invoke(); }); @@ -137,15 +138,16 @@ namespace osu.Game.Screens.Play Content.FadeColour(Color4.Gray, duration); } - public void RemoveFilters() + public void RemoveFilters(bool resetTrackFrequency = true) { + if (resetTrackFrequency) + track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); + if (filters.Parent == null) return; RemoveInternal(filters); filters.Dispose(); - - track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); } protected override void Update() From a0e25d18cd5f809bfade52754a652d318d47a805 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Nov 2021 18:24:00 +0900 Subject: [PATCH 167/169] Expose more of `WorkingBeatmap` via interface --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 65 +++++++++++++++++++++++++--- osu.Game/Beatmaps/WorkingBeatmap.cs | 21 ++++----- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index a916b37b85..22a922db59 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -17,33 +17,69 @@ namespace osu.Game.Beatmaps { public interface IWorkingBeatmap { + IBeatmapInfo BeatmapInfo { get; } + + IBeatmapSetInfo BeatmapSetInfo => BeatmapInfo.BeatmapSet; + + IBeatmapMetadataInfo Metadata => BeatmapInfo.Metadata; + /// - /// Retrieves the which this represents. + /// Whether the Beatmap has finished loading. + /// + public bool BeatmapLoaded { get; } + + /// + /// Whether the Background has finished loading. + /// + public bool BackgroundLoaded { get; } + + /// + /// Whether the Waveform has finished loading. + /// + public bool WaveformLoaded { get; } + + /// + /// Whether the Storyboard has finished loading. + /// + public bool StoryboardLoaded { get; } + + /// + /// Whether the Skin has finished loading. + /// + public bool SkinLoaded { get; } + + /// + /// Whether the Track has finished loading. + /// + public bool TrackLoaded { get; } + + /// + /// Retrieves the which this represents. /// IBeatmap Beatmap { get; } /// - /// Retrieves the background for this . + /// Retrieves the background for this . /// Texture Background { get; } /// - /// Retrieves the for the of this . + /// Retrieves the for the of this . /// Waveform Waveform { get; } /// - /// Retrieves the which this provides. + /// Retrieves the which this provides. /// Storyboard Storyboard { get; } /// - /// Retrieves the which this provides. + /// Retrieves the which this provides. /// ISkin Skin { get; } /// - /// Retrieves the which this has loaded. + /// Retrieves the which this has loaded. /// Track Track { get; } @@ -67,7 +103,7 @@ namespace osu.Game.Beatmaps /// /// /// In a standard game context, the loading of the track is managed solely by MusicController, which will - /// automatically load the track of the current global IBindable WorkingBeatmap. + /// automatically load the track of the current global IBindable IWorkingBeatmap. /// As such, this method should only be called in very special scenarios, such as external tests or apps which are /// outside of the game context. /// @@ -79,5 +115,20 @@ namespace osu.Game.Beatmaps /// /// The storage path to the file. Stream GetStream(string storagePath); + + /// + /// Beings loading the contents of this asynchronously. + /// + public void BeginAsyncLoad(); + + /// + /// Cancels the asynchronous loading of the contents of this . + /// + public void CancelAsyncLoad(); + + /// + /// Reads the correct track restart point from beatmap metadata and sets looping to enabled. + /// + void PrepareTrackForPreviewLooping(); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index d2c0f7de0f..51eea94d3a 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -28,8 +28,10 @@ namespace osu.Game.Beatmaps { public readonly BeatmapInfo BeatmapInfo; + // ReSharper disable once FieldHidesInterfacePropertyWithDefaultImplementation public readonly BeatmapSetInfo BeatmapSetInfo; + // ReSharper disable once FieldHidesInterfacePropertyWithDefaultImplementation public readonly BeatmapMetadata Metadata; protected AudioManager AudioManager { get; } @@ -89,6 +91,9 @@ namespace osu.Game.Beatmaps var rulesetInstance = ruleset.CreateInstance(); + if (rulesetInstance == null) + throw new RulesetLoadException("Creating ruleset instance failed when attempting to create playable beatmap."); + IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); // Check if the beatmap can be converted @@ -176,17 +181,8 @@ namespace osu.Game.Beatmaps private CancellationTokenSource loadCancellation = new CancellationTokenSource(); - /// - /// Beings loading the contents of this asynchronously. - /// - public void BeginAsyncLoad() - { - loadBeatmapAsync(); - } + public void BeginAsyncLoad() => loadBeatmapAsync(); - /// - /// Cancels the asynchronous loading of the contents of this . - /// public void CancelAsyncLoad() { lock (beatmapFetchLock) @@ -234,6 +230,8 @@ namespace osu.Game.Beatmaps public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; + IBeatmapInfo IWorkingBeatmap.BeatmapInfo => BeatmapInfo; + public IBeatmap Beatmap { get @@ -273,9 +271,6 @@ namespace osu.Game.Beatmaps [NotNull] public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000); - /// - /// Reads the correct track restart point from beatmap metadata and sets looping to enabled. - /// public void PrepareTrackForPreviewLooping() { Track.Looping = true; From a90cfb4a2f9b4cf839e3ad0daac1219c984b96e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Nov 2021 19:30:46 +0900 Subject: [PATCH 168/169] Implement remaining properties via implicit implementation rather than interface methods --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 ++-- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 22a922db59..ba887edf62 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -19,9 +19,9 @@ namespace osu.Game.Beatmaps { IBeatmapInfo BeatmapInfo { get; } - IBeatmapSetInfo BeatmapSetInfo => BeatmapInfo.BeatmapSet; + IBeatmapSetInfo BeatmapSetInfo { get; } - IBeatmapMetadataInfo Metadata => BeatmapInfo.Metadata; + IBeatmapMetadataInfo Metadata { get; } /// /// Whether the Beatmap has finished loading. diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 51eea94d3a..bba836484f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -231,6 +231,8 @@ namespace osu.Game.Beatmaps public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; IBeatmapInfo IWorkingBeatmap.BeatmapInfo => BeatmapInfo; + IBeatmapMetadataInfo IWorkingBeatmap.Metadata => Metadata; + IBeatmapSetInfo IWorkingBeatmap.BeatmapSetInfo => BeatmapSetInfo; public IBeatmap Beatmap { From 1d87b47fecb9ed281d2c641a7e15262de827e55d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 15 Nov 2021 19:40:31 +0900 Subject: [PATCH 169/169] Remove now-unnecessary R# disables --- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index bba836484f..f68f34f673 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -27,11 +27,7 @@ namespace osu.Game.Beatmaps public abstract class WorkingBeatmap : IWorkingBeatmap { public readonly BeatmapInfo BeatmapInfo; - - // ReSharper disable once FieldHidesInterfacePropertyWithDefaultImplementation public readonly BeatmapSetInfo BeatmapSetInfo; - - // ReSharper disable once FieldHidesInterfacePropertyWithDefaultImplementation public readonly BeatmapMetadata Metadata; protected AudioManager AudioManager { get; }