From e6ee360511e85c82158aa9e1019fa5f1ebbd361e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 15 Oct 2021 00:10:39 +0900
Subject: [PATCH 01/11] Fix intermittent multiplayer tests

---
 .../Online/Multiplayer/MultiplayerClient.cs   | 27 ++++++++++---------
 .../Multiplayer/TestMultiplayerClient.cs      | 13 +++++++--
 2 files changed, 26 insertions(+), 14 deletions(-)

diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 75bbaec0ef..49a4526133 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -359,22 +359,25 @@ namespace osu.Game.Online.Multiplayer
             if (Room == null)
                 return;
 
-            await PopulateUser(user).ConfigureAwait(false);
-
-            Scheduler.Add(() =>
+            await Task.Run(async () =>
             {
-                if (Room == null)
-                    return;
+                await PopulateUser(user).ConfigureAwait(false);
 
-                // for sanity, ensure that there can be no duplicate users in the room user list.
-                if (Room.Users.Any(existing => existing.UserID == user.UserID))
-                    return;
+                Scheduler.Add(() =>
+                {
+                    if (Room == null)
+                        return;
 
-                Room.Users.Add(user);
+                    // for sanity, ensure that there can be no duplicate users in the room user list.
+                    if (Room.Users.Any(existing => existing.UserID == user.UserID))
+                        return;
 
-                UserJoined?.Invoke(user);
-                RoomUpdated?.Invoke();
-            }, false);
+                    Room.Users.Add(user);
+
+                    UserJoined?.Invoke(user);
+                    RoomUpdated?.Invoke();
+                });
+            }).ConfigureAwait(false);
         }
 
         Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) =>
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index 2c0ca0b872..9aa36f8dbd 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -53,7 +53,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
         public MultiplayerRoomUser AddUser(User user, bool markAsPlaying = false)
         {
             var roomUser = new MultiplayerRoomUser(user.Id) { User = user };
-            ((IMultiplayerClient)this).UserJoined(roomUser);
+
+            addUser(roomUser);
 
             if (markAsPlaying)
                 PlayingUserIds.Add(user.Id);
@@ -61,7 +62,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
             return roomUser;
         }
 
-        public void AddNullUser() => ((IMultiplayerClient)this).UserJoined(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID));
+        public void AddNullUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID));
+
+        private void addUser(MultiplayerRoomUser user)
+        {
+            ((IMultiplayerClient)this).UserJoined(user).Wait();
+
+            // We want the user to be immediately available for testing, so force a scheduler update.
+            Scheduler.Update();
+        }
 
         public void RemoveUser(User user)
         {

From eed8fa8d6959845d81553aa5d78e1844f4ea18d0 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 15 Oct 2021 00:20:45 +0900
Subject: [PATCH 02/11] Expand comment a bit

---
 osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index 9aa36f8dbd..5e4e5942d9 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
         {
             ((IMultiplayerClient)this).UserJoined(user).Wait();
 
-            // We want the user to be immediately available for testing, so force a scheduler update.
+            // We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation.
             Scheduler.Update();
         }
 

From 6b1534f5a6e3658d802b19744e5f59c6feae1eb3 Mon Sep 17 00:00:00 2001
From: Jason Won <jasonwon628@gmail.com>
Date: Thu, 14 Oct 2021 18:30:55 -0400
Subject: [PATCH 03/11] Add "ghost" mod for osu

---
 osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs | 67 +++++++++++++++++++++++
 osu.Game.Rulesets.Osu/OsuRuleset.cs       |  1 +
 2 files changed, 68 insertions(+)
 create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs

diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs
new file mode 100644
index 0000000000..d371686d10
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs
@@ -0,0 +1,67 @@
+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.Mods;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Bindables;
+using osu.Framework.Localisation;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Configuration;
+using osu.Game.Overlays.Settings;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+    public class OsuModGhost : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor
+    {
+        public override string Name => "Ghost";
+        public override string Acronym => "G";
+        public override ModType Type => ModType.Fun;
+        public override IconUsage? Icon => FontAwesome.Solid.Ghost;
+        public override string Description => "Where's the cursor?";
+        public override double ScoreMultiplier => 1;
+        
+        private readonly BindableNumber<float> ghostAlpha = new BindableFloat(0);
+        private BindableNumber<int> currentCombo;
+
+        [SettingSource(
+            "Hidden at combo",
+            "The combo count at which the cursor becomes completely hidden",
+            SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
+        )]
+        public BindableInt HiddenComboCount { get; } = new BindableInt
+        {
+            Default = 10,
+            Value = 10,
+            MinValue = 0,
+            MaxValue = 50,
+        };
+
+        public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
+        public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
+        {
+            currentCombo = scoreProcessor.Combo.GetBoundCopy();
+            currentCombo.BindValueChanged(combo =>
+            {
+                float dimFactor = HiddenComboCount.Value == 0 ? 1 : (float)combo.NewValue / HiddenComboCount.Value;
+
+                scoreProcessor.TransformBindableTo(ghostAlpha, 1-dimFactor, 100, Easing.OutQuint);
+            }, true);
+        }
+
+
+        public virtual void Update(Playfield playfield)
+        {
+            playfield.Cursor.Alpha = ghostAlpha.Value;
+        }
+        
+    }
+
+    public class HiddenComboSlider : OsuSliderBar<int>
+    {
+        public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText;
+    }
+}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index f4a93a571d..947ecaae82 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Osu
                         new OsuModBarrelRoll(),
                         new OsuModApproachDifferent(),
                         new OsuModMuted(),
+                        new OsuModGhost(),
                     };
 
                 case ModType.System:

From 23b50a054beaa41a03c09cb1c6223f85156dafb4 Mon Sep 17 00:00:00 2001
From: Jason Won <jasonwon628@gmail.com>
Date: Thu, 14 Oct 2021 20:50:47 -0400
Subject: [PATCH 04/11] address comments

---
 osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs | 36 ++++++++++++++---------
 1 file changed, 22 insertions(+), 14 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs
index d371686d10..7cb9fadf03 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs
@@ -3,10 +3,10 @@
 
 using osu.Game.Rulesets.UI;
 using osu.Game.Rulesets.Mods;
-using osu.Framework.Graphics;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Bindables;
 using osu.Framework.Localisation;
+using osu.Framework.Utils;
 using osu.Game.Graphics.UserInterface;
 using osu.Game.Configuration;
 using osu.Game.Overlays.Settings;
@@ -17,14 +17,18 @@ namespace osu.Game.Rulesets.Osu.Mods
 {
     public class OsuModGhost : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor
     {
+        public const float CURSOR_ALPHA_TRANSITION_DURATION = 100;
         public override string Name => "Ghost";
-        public override string Acronym => "G";
+        public override string Acronym => "GS";
         public override ModType Type => ModType.Fun;
         public override IconUsage? Icon => FontAwesome.Solid.Ghost;
         public override string Description => "Where's the cursor?";
         public override double ScoreMultiplier => 1;
-        
-        private readonly BindableNumber<float> ghostAlpha = new BindableFloat(0);
+        private double transitionProgress = 0;
+        private float currentCursorAlpha = 1;
+        private float startCursorAlpha = 1;
+        private float targetCursorAlpha = 0;
+
         private BindableNumber<int> currentCombo;
 
         [SettingSource(
@@ -43,21 +47,25 @@ namespace osu.Game.Rulesets.Osu.Mods
         public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
         public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
         {
-            currentCombo = scoreProcessor.Combo.GetBoundCopy();
-            currentCombo.BindValueChanged(combo =>
-            {
-                float dimFactor = HiddenComboCount.Value == 0 ? 1 : (float)combo.NewValue / HiddenComboCount.Value;
-
-                scoreProcessor.TransformBindableTo(ghostAlpha, 1-dimFactor, 100, Easing.OutQuint);
-            }, true);
+            if (HiddenComboCount.Value != 0) {
+                currentCombo = scoreProcessor.Combo.GetBoundCopy();
+                currentCombo.BindValueChanged(combo =>
+                {
+                    targetCursorAlpha = 1 - (float)combo.NewValue / HiddenComboCount.Value;
+                    startCursorAlpha = currentCursorAlpha;
+                    transitionProgress = 0;
+                }, true);
+            }
         }
 
-
         public virtual void Update(Playfield playfield)
         {
-            playfield.Cursor.Alpha = ghostAlpha.Value;
+            if (transitionProgress < CURSOR_ALPHA_TRANSITION_DURATION) {
+                transitionProgress += playfield.Time.Elapsed;
+                currentCursorAlpha = (float)Interpolation.Lerp(startCursorAlpha, targetCursorAlpha, transitionProgress/CURSOR_ALPHA_TRANSITION_DURATION);
+                playfield.Cursor.Alpha = currentCursorAlpha;
+            }
         }
-        
     }
 
     public class HiddenComboSlider : OsuSliderBar<int>

From 2dfc42dd53ed48e187a94c2c0fcd34c867948566 Mon Sep 17 00:00:00 2001
From: Jason Won <jasonwon628@gmail.com>
Date: Thu, 14 Oct 2021 23:34:43 -0400
Subject: [PATCH 05/11] revert interpolation changes + fix cursor trail

---
 osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs | 24 +++++++++--------------
 1 file changed, 9 insertions(+), 15 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs
index 7cb9fadf03..cc8491876b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs
@@ -1,12 +1,13 @@
 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
 // See the LICENCE file in the repository root for full licence text.
 
+using System;
 using osu.Game.Rulesets.UI;
 using osu.Game.Rulesets.Mods;
+using osu.Framework.Graphics;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Bindables;
 using osu.Framework.Localisation;
-using osu.Framework.Utils;
 using osu.Game.Graphics.UserInterface;
 using osu.Game.Configuration;
 using osu.Game.Overlays.Settings;
@@ -24,11 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
         public override IconUsage? Icon => FontAwesome.Solid.Ghost;
         public override string Description => "Where's the cursor?";
         public override double ScoreMultiplier => 1;
-        private double transitionProgress = 0;
-        private float currentCursorAlpha = 1;
-        private float startCursorAlpha = 1;
-        private float targetCursorAlpha = 0;
-
+        private readonly BindableFloat cursorAlpha = new BindableFloat();
         private BindableNumber<int> currentCombo;
 
         [SettingSource(
@@ -45,26 +42,23 @@ namespace osu.Game.Rulesets.Osu.Mods
         };
 
         public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
+
         public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
         {
-            if (HiddenComboCount.Value != 0) {
+            if (HiddenComboCount.Value != 0)
+            {
                 currentCombo = scoreProcessor.Combo.GetBoundCopy();
                 currentCombo.BindValueChanged(combo =>
                 {
-                    targetCursorAlpha = 1 - (float)combo.NewValue / HiddenComboCount.Value;
-                    startCursorAlpha = currentCursorAlpha;
-                    transitionProgress = 0;
+                    float targetCursorAlpha = (float)Math.Max(1e-3, 1 - (float)combo.NewValue / HiddenComboCount.Value);
+                    scoreProcessor.TransformBindableTo(cursorAlpha, targetCursorAlpha, CURSOR_ALPHA_TRANSITION_DURATION, Easing.OutQuint);
                 }, true);
             }
         }
 
         public virtual void Update(Playfield playfield)
         {
-            if (transitionProgress < CURSOR_ALPHA_TRANSITION_DURATION) {
-                transitionProgress += playfield.Time.Elapsed;
-                currentCursorAlpha = (float)Interpolation.Lerp(startCursorAlpha, targetCursorAlpha, transitionProgress/CURSOR_ALPHA_TRANSITION_DURATION);
-                playfield.Cursor.Alpha = currentCursorAlpha;
-            }
+            playfield.Cursor.Alpha = cursorAlpha.Value;
         }
     }
 

From 7ab028576ed581073f338eb1abaa11c58620a2cb Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 15 Oct 2021 12:54:19 +0900
Subject: [PATCH 06/11] Change `RoundedButton` to source from overlay colour
 provider

---
 osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
index 27e28f1e03..23ebc6e98d 100644
--- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
@@ -2,10 +2,12 @@
 // See the LICENCE file in the repository root for full licence text.
 
 using System.Collections.Generic;
+using JetBrains.Annotations;
 using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays;
 
 namespace osu.Game.Graphics.UserInterfaceV2
 {
@@ -23,10 +25,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
             }
         }
 
-        [BackgroundDependencyLoader]
-        private void load(OsuColour colours)
+        [BackgroundDependencyLoader(true)]
+        private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours)
         {
-            BackgroundColour = colours.Blue3;
+            BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3;
         }
 
         protected override void LoadComplete()

From 538d980072aa746519f8e98121f2b2e72d8e75d4 Mon Sep 17 00:00:00 2001
From: Jason Won <jasonwon628@gmail.com>
Date: Fri, 15 Oct 2021 00:22:57 -0400
Subject: [PATCH 07/11] Rename Mod

---
 .../Mods/{OsuModGhost.cs => OsuModNoScope.cs}             | 8 ++++----
 osu.Game.Rulesets.Osu/OsuRuleset.cs                       | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)
 rename osu.Game.Rulesets.Osu/Mods/{OsuModGhost.cs => OsuModNoScope.cs} (90%)

diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
similarity index 90%
rename from osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs
rename to osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
index cc8491876b..56c807eacd 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGhost.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
@@ -16,13 +16,13 @@ using osu.Game.Scoring;
 
 namespace osu.Game.Rulesets.Osu.Mods
 {
-    public class OsuModGhost : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor
+    public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor
     {
         public const float CURSOR_ALPHA_TRANSITION_DURATION = 100;
-        public override string Name => "Ghost";
-        public override string Acronym => "GS";
+        public override string Name => "No Scope";
+        public override string Acronym => "NS";
         public override ModType Type => ModType.Fun;
-        public override IconUsage? Icon => FontAwesome.Solid.Ghost;
+        public override IconUsage? Icon => FontAwesome.Solid.EyeSlash;
         public override string Description => "Where's the cursor?";
         public override double ScoreMultiplier => 1;
         private readonly BindableFloat cursorAlpha = new BindableFloat();
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 947ecaae82..ee4712c3b8 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu
                         new OsuModBarrelRoll(),
                         new OsuModApproachDifferent(),
                         new OsuModMuted(),
-                        new OsuModGhost(),
+                        new OsuModNoScope(),
                     };
 
                 case ModType.System:

From c9d8645341ac3f68d2696427b62cd4c8fd1fe63e Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 15 Oct 2021 13:26:31 +0900
Subject: [PATCH 08/11] Fix startup import test waiting on potentially
 incorrect notification type

---
 osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs
index cb7c334656..bd723eeed6 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs
@@ -4,7 +4,7 @@
 using System.Linq;
 using NUnit.Framework;
 using osu.Framework.Testing;
-using osu.Game.Database;
+using osu.Game.Overlays.Notifications;
 using osu.Game.Tests.Resources;
 
 namespace osu.Game.Tests.Visual.Navigation
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Navigation
         [Test]
         public void TestImportCreatedNotification()
         {
-            AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType<ImportProgressNotification>().Count() == 1);
+            AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType<ProgressCompletionNotification>().Count() == 1);
         }
     }
 }

From 80dfd11c90a635e18eaa8513700ad412f13acda5 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 15 Oct 2021 13:27:54 +0900
Subject: [PATCH 09/11] Remove unnecessary extra task

Further testing shows continuations also run before the .Wait() returns.
---
 .../Online/Multiplayer/MultiplayerClient.cs   | 27 +++++++++----------
 1 file changed, 12 insertions(+), 15 deletions(-)

diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 49a4526133..28505f6b0e 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -359,25 +359,22 @@ namespace osu.Game.Online.Multiplayer
             if (Room == null)
                 return;
 
-            await Task.Run(async () =>
+            await PopulateUser(user).ConfigureAwait(false);
+
+            Scheduler.Add(() =>
             {
-                await PopulateUser(user).ConfigureAwait(false);
+                if (Room == null)
+                    return;
 
-                Scheduler.Add(() =>
-                {
-                    if (Room == null)
-                        return;
+                // for sanity, ensure that there can be no duplicate users in the room user list.
+                if (Room.Users.Any(existing => existing.UserID == user.UserID))
+                    return;
 
-                    // for sanity, ensure that there can be no duplicate users in the room user list.
-                    if (Room.Users.Any(existing => existing.UserID == user.UserID))
-                        return;
+                Room.Users.Add(user);
 
-                    Room.Users.Add(user);
-
-                    UserJoined?.Invoke(user);
-                    RoomUpdated?.Invoke();
-                });
-            }).ConfigureAwait(false);
+                UserJoined?.Invoke(user);
+                RoomUpdated?.Invoke();
+            });
         }
 
         Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) =>

From 6a80a417bdc7f8520bcc76577d93629ec8a2991e Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 15 Oct 2021 14:25:50 +0900
Subject: [PATCH 10/11] Use `Interpolation.Lerp` instead of transforms

Better handles cases where the combo may be changing faster than the
transition length.
---
 osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 27 +++++++++++++--------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
index 56c807eacd..09870f2758 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Bindables;
 using osu.Framework.Localisation;
+using osu.Framework.Utils;
 using osu.Game.Graphics.UserInterface;
 using osu.Game.Configuration;
 using osu.Game.Overlays.Settings;
@@ -19,15 +20,18 @@ namespace osu.Game.Rulesets.Osu.Mods
     public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor
     {
         public const float CURSOR_ALPHA_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 readonly BindableFloat cursorAlpha = new BindableFloat();
+
         private BindableNumber<int> currentCombo;
 
+        private float targetAlpha;
+
         [SettingSource(
             "Hidden at combo",
             "The combo count at which the cursor becomes completely hidden",
@@ -43,22 +47,25 @@ namespace osu.Game.Rulesets.Osu.Mods
 
         public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
 
+        /// <summary>
+        /// Slightly higher than the cutoff for <see cref="Drawable.IsPresent"/>.
+        /// </summary>
+        private const float min_alpha = 0.0002f;
+
         public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
         {
-            if (HiddenComboCount.Value != 0)
+            if (HiddenComboCount.Value == 0) return;
+
+            currentCombo = scoreProcessor.Combo.GetBoundCopy();
+            currentCombo.BindValueChanged(combo =>
             {
-                currentCombo = scoreProcessor.Combo.GetBoundCopy();
-                currentCombo.BindValueChanged(combo =>
-                {
-                    float targetCursorAlpha = (float)Math.Max(1e-3, 1 - (float)combo.NewValue / HiddenComboCount.Value);
-                    scoreProcessor.TransformBindableTo(cursorAlpha, targetCursorAlpha, CURSOR_ALPHA_TRANSITION_DURATION, Easing.OutQuint);
-                }, true);
-            }
+                targetAlpha = Math.Max(min_alpha, 1 - (float)combo.NewValue / HiddenComboCount.Value);
+            }, true);
         }
 
         public virtual void Update(Playfield playfield)
         {
-            playfield.Cursor.Alpha = cursorAlpha.Value;
+            playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / CURSOR_ALPHA_TRANSITION_DURATION, 0, 1));
         }
     }
 

From 3a7eb7dd25f8e2da74684ec3395d79f5a917e92f Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 15 Oct 2021 14:27:20 +0900
Subject: [PATCH 11/11] Make const private

---
 osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
index 09870f2758..c48cbd9992 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
@@ -19,7 +19,12 @@ namespace osu.Game.Rulesets.Osu.Mods
 {
     public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor
     {
-        public const float CURSOR_ALPHA_TRANSITION_DURATION = 100;
+        /// <summary>
+        /// Slightly higher than the cutoff for <see cref="Drawable.IsPresent"/>.
+        /// </summary>
+        private const float min_alpha = 0.0002f;
+
+        private const float transition_duration = 100;
 
         public override string Name => "No Scope";
         public override string Acronym => "NS";
@@ -47,11 +52,6 @@ namespace osu.Game.Rulesets.Osu.Mods
 
         public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
 
-        /// <summary>
-        /// Slightly higher than the cutoff for <see cref="Drawable.IsPresent"/>.
-        /// </summary>
-        private const float min_alpha = 0.0002f;
-
         public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
         {
             if (HiddenComboCount.Value == 0) return;
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Mods
 
         public virtual void Update(Playfield playfield)
         {
-            playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / CURSOR_ALPHA_TRANSITION_DURATION, 0, 1));
+            playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / transition_duration, 0, 1));
         }
     }