From d19cdbdefb731664dda045512f0f7b07486cde75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Mon, 2 Oct 2023 22:31:47 +0200 Subject: [PATCH 01/37] Add `InvitePlayer` method to multiplayer server --- osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs | 7 +++++++ osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 ++ osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 10 ++++++++++ 3 files changed, 19 insertions(+) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index b7a5faf7c9..64cd6df24d 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -99,5 +99,12 @@ namespace osu.Game.Online.Multiplayer /// /// The item to remove. Task RemovePlaylistItem(long playlistItemId); + + /// + /// Invites a player to the current room. + /// + /// + /// + Task InvitePlayer(int userId); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 5716b7ad3b..957f55406a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -260,6 +260,8 @@ namespace osu.Game.Online.Multiplayer protected abstract Task LeaveRoomInternal(); + public abstract Task InvitePlayer(int userId); + /// /// Change the current settings. /// diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 8ff0ce4065..ebe89cf018 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -106,6 +106,16 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); } + public override Task InvitePlayer(int userId) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + Debug.Assert(connection != null); + + return connection.InvokeAsync(nameof(IMultiplayerServer.InvitePlayer), userId); + } + public override Task TransferHost(int userId) { if (!IsConnected.Value) From 574dc67a9ed12762d4a56b2bf20ab5cb128df7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Mon, 2 Oct 2023 22:53:28 +0200 Subject: [PATCH 02/37] Add `Invited` task to multiplayer client --- osu.Game/Online/Multiplayer/IMultiplayerClient.cs | 7 +++++++ osu.Game/Online/Multiplayer/MultiplayerClient.cs | 15 +++++++++++++++ .../Online/Multiplayer/OnlineMultiplayerClient.cs | 1 + .../Visual/Multiplayer/TestMultiplayerClient.cs | 5 +++++ 4 files changed, 28 insertions(+) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 995bac1af5..f59ded93f8 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -42,6 +42,13 @@ namespace osu.Game.Online.Multiplayer /// The user. Task UserKicked(MultiplayerRoomUser user); + /// + /// Signals that a user has been invited into a multiplayer room. + /// + /// Id of user that invited the player. + /// The room the user got invited to. + Task Invited(int invitedBy, MultiplayerRoom room); + /// /// Signal that the host of the room has changed. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 957f55406a..e438f9f96d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -442,6 +442,21 @@ namespace osu.Game.Online.Multiplayer return handleUserLeft(user, UserKicked); } + async Task IMultiplayerClient.Invited(int invitedBy, MultiplayerRoom room) + { + var user = await userLookupCache.GetUserAsync(invitedBy).ConfigureAwait(false); + + if (user == null) return; + + Scheduler.Add(() => + { + PostNotification?.Invoke(new SimpleNotification + { + Text = "You got invited into a multiplayer match by " + user.Username + "!", + }); + }); + } + private void addUserToAPIRoom(MultiplayerRoomUser user) { Debug.Assert(APIRoom != null); diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index ebe89cf018..0e327bbc83 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -50,6 +50,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined); connection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft); connection.On(nameof(IMultiplayerClient.UserKicked), ((IMultiplayerClient)this).UserKicked); + connection.On(nameof(IMultiplayerClient.Invited), ((IMultiplayerClient)this).Invited); connection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); connection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index c27e30d5bb..d44eff47a3 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -263,6 +263,11 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } + public override Task InvitePlayer(int userId) + { + return Task.CompletedTask; + } + public override Task TransferHost(int userId) { userId = clone(userId); From 7629b725a29834339bc029d1bd754c19e75a5199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Mon, 2 Oct 2023 22:55:53 +0200 Subject: [PATCH 03/37] Add invite button to UserPanel context menu --- osu.Game/Users/UserPanel.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index e2dc511391..29a6c65555 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -18,6 +19,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Resources.Localisation.Web; using osu.Game.Localisation; +using osu.Game.Online.Multiplayer; namespace osu.Game.Users { @@ -61,6 +63,9 @@ namespace osu.Game.Users [Resolved] protected OsuColour Colours { get; private set; } = null!; + [Resolved] + private MultiplayerClient multiplayerClient { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { @@ -117,6 +122,15 @@ namespace osu.Game.Users })); } + if ( + User.IsOnline && + multiplayerClient.Room != null && + multiplayerClient.Room.Users.All(u => u.UserID != User.Id) + ) + { + items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(User.Id))); + } + return items.ToArray(); } } From 251e4d4de9d1bb5e44353ffec1f5bdacc546a3f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Mon, 2 Oct 2023 23:10:29 +0200 Subject: [PATCH 04/37] Add localisation for inviting a player --- osu.Game/Localisation/ContextMenuStrings.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Localisation/ContextMenuStrings.cs b/osu.Game/Localisation/ContextMenuStrings.cs index 8bc213016b..029fba67d8 100644 --- a/osu.Game/Localisation/ContextMenuStrings.cs +++ b/osu.Game/Localisation/ContextMenuStrings.cs @@ -19,6 +19,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ViewBeatmap => new TranslatableString(getKey(@"view_beatmap"), @"View beatmap"); + /// + /// "Invite player" + /// + public static LocalisableString InvitePlayer => new TranslatableString(getKey(@"invite_player"), @"Invite player"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } From e81695bcacdde50284133c01db2fd33ecafabac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Mon, 2 Oct 2023 23:10:51 +0200 Subject: [PATCH 05/37] Display avatar in invitation notification --- .../TestSceneNotificationOverlay.cs | 17 ++++ .../Online/Multiplayer/MultiplayerClient.cs | 7 +- osu.Game/Overlays/NotificationOverlay.cs | 2 +- .../Notifications/UserAvatarNotification.cs | 78 +++++++++++++++++++ 4 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Overlays/Notifications/UserAvatarNotification.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 4d3ae079e3..07bd722322 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -4,13 +4,17 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Database; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -31,6 +35,8 @@ namespace osu.Game.Tests.Visual.UserInterface public double TimeToCompleteProgress { get; set; } = 2000; + private readonly UserLookupCache userLookupCache = new TestUserLookupCache(); + [SetUp] public void SetUp() => Schedule(() => { @@ -60,6 +66,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep(@"simple #2", sendAmazingNotification); AddStep(@"progress #1", sendUploadProgress); AddStep(@"progress #2", sendDownloadProgress); + AddStep(@"User notification", sendUserNotification); checkProgressingCount(2); @@ -537,6 +544,16 @@ namespace osu.Game.Tests.Visual.UserInterface progressingNotifications.Add(n); } + private async void sendUserNotification() + { + var user = await userLookupCache.GetUserAsync(0).ConfigureAwait(true); + if (user == null) return; + + var n = new UserAvatarNotification(user, $"{user.Username} is telling you to NOT download Haitai!"); + + notificationOverlay.Post(n); + } + private void sendUploadProgress() { var n = new ProgressNotification diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index e438f9f96d..bb953bae58 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -450,10 +450,9 @@ namespace osu.Game.Online.Multiplayer Scheduler.Add(() => { - PostNotification?.Invoke(new SimpleNotification - { - Text = "You got invited into a multiplayer match by " + user.Username + "!", - }); + PostNotification?.Invoke( + new UserAvatarNotification(user, $"{user.Username} invited you to a multiplayer match!") + ); }); } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 6e0ea23dd1..67cf868fb0 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -113,7 +113,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, Children = new[] { - new NotificationSection(AccountsStrings.NotificationsTitle, new[] { typeof(SimpleNotification) }), + new NotificationSection(AccountsStrings.NotificationsTitle, new[] { typeof(SimpleNotification), typeof(UserAvatarNotification) }), new NotificationSection(NotificationsStrings.RunningTasks, new[] { typeof(ProgressNotification) }), } } diff --git a/osu.Game/Overlays/Notifications/UserAvatarNotification.cs b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs new file mode 100644 index 0000000000..191d63a76f --- /dev/null +++ b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs @@ -0,0 +1,78 @@ +// 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.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users.Drawables; + +namespace osu.Game.Overlays.Notifications +{ + public partial class UserAvatarNotification : Notification + { + private LocalisableString text; + + public override LocalisableString Text + { + get => text; + set + { + text = value; + if (textDrawable != null) + textDrawable.Text = text; + } + } + + private TextFlowContainer? textDrawable; + + private APIUser user; + + public UserAvatarNotification(APIUser user, LocalisableString text) + { + this.user = user; + Text = text; + } + + private DrawableAvatar? avatar; + + protected override IconUsage CloseButtonIcon => FontAwesome.Solid.Times; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IconContent.Masking = true; + + // Workaround for the corner radius on parent's mask breaking if we add masking to IconContent + IconContent.CornerRadius = 6; + + IconContent.AddRange(new Drawable[] + { + new Box() + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + }); + + avatar = new DrawableAvatar(user) + { + FillMode = FillMode.Fill, + }; + LoadComponentAsync(avatar, IconContent.Add); + + Content.Add(textDrawable = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 14, weight: FontWeight.Medium)) + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Text = text + }); + } + } +} From 3879775219991cf44e9f8f44e320dc233637f286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Mon, 2 Oct 2023 23:20:24 +0200 Subject: [PATCH 06/37] Add room name to invite notification --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index bb953bae58..6e46a5a3b9 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -451,7 +451,7 @@ namespace osu.Game.Online.Multiplayer Scheduler.Add(() => { PostNotification?.Invoke( - new UserAvatarNotification(user, $"{user.Username} invited you to a multiplayer match!") + new UserAvatarNotification(user, $"{user.Username} invited you to a multiplayer match:\"{room.Settings.Name}\"!") ); }); } From 8e73dbc894d4297b46aaf1a881d40acda797445f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Tue, 3 Oct 2023 01:22:25 +0200 Subject: [PATCH 07/37] Load api room before displaying notification --- .../Online/Multiplayer/IMultiplayerClient.cs | 5 ++- .../Online/Multiplayer/MultiplayerClient.cs | 41 +++++++++++++++++-- .../Multiplayer/OnlineMultiplayerClient.cs | 2 +- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index f59ded93f8..ba9e8a237c 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -46,8 +46,9 @@ namespace osu.Game.Online.Multiplayer /// Signals that a user has been invited into a multiplayer room. /// /// Id of user that invited the player. - /// The room the user got invited to. - Task Invited(int invitedBy, MultiplayerRoom room); + /// Id of the room the user got invited to. + /// Password to join the room. + Task Invited(int invitedBy, long roomID, string password); /// /// Signal that the host of the room has changed. diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 6e46a5a3b9..61c2fa495a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Database; @@ -30,6 +31,8 @@ namespace osu.Game.Online.Multiplayer { public Action? PostNotification { protected get; set; } + public Action? InviteAccepted { protected get; set; } + /// /// Invoked when any change occurs to the multiplayer room. /// @@ -442,20 +445,50 @@ namespace osu.Game.Online.Multiplayer return handleUserLeft(user, UserKicked); } - async Task IMultiplayerClient.Invited(int invitedBy, MultiplayerRoom room) + async Task IMultiplayerClient.Invited(int invitedBy, long roomID, string password) { - var user = await userLookupCache.GetUserAsync(invitedBy).ConfigureAwait(false); + var loadUserTask = userLookupCache.GetUserAsync(invitedBy); + var loadRoomTask = loadRoom(roomID); - if (user == null) return; + await Task.WhenAll(loadUserTask, loadRoomTask).ConfigureAwait(false); + + APIUser? apiUser = loadUserTask.GetResultSafely(); + Room? apiRoom = loadRoomTask.GetResultSafely(); + + if (apiUser == null || apiRoom == null) return; Scheduler.Add(() => { PostNotification?.Invoke( - new UserAvatarNotification(user, $"{user.Username} invited you to a multiplayer match:\"{room.Settings.Name}\"!") + new UserAvatarNotification(apiUser, $"{apiUser.Username} invited you to a multiplayer match:\"{apiRoom.Name}\"!") + { + Activated = () => + { + InviteAccepted?.Invoke(apiRoom, password); + return true; + } + } ); }); } + private Task loadRoom(long id) + { + return Task.Run(() => + { + var t = new TaskCompletionSource(); + var request = new GetRoomRequest(id); + + request.Success += room => t.TrySetResult(room); + + request.Failure += e => t.TrySetResult(null); + + API.Queue(request); + + return t.Task; + }); + } + private void addUserToAPIRoom(MultiplayerRoomUser user) { Debug.Assert(APIRoom != null); diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 0e327bbc83..5b5741ef1c 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -50,7 +50,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined); connection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft); connection.On(nameof(IMultiplayerClient.UserKicked), ((IMultiplayerClient)this).UserKicked); - connection.On(nameof(IMultiplayerClient.Invited), ((IMultiplayerClient)this).Invited); + connection.On(nameof(IMultiplayerClient.Invited), ((IMultiplayerClient)this).Invited); connection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); connection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); From a171fa7649c7c4e59544a3c7a167694fba51bfe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Tue, 3 Oct 2023 01:31:30 +0200 Subject: [PATCH 08/37] Join multiplayer match when clicking the invite notification --- osu.Game/OsuGame.cs | 22 +++++++++++++++++++ .../OnlinePlay/Multiplayer/Multiplayer.cs | 6 +++++ .../Screens/OnlinePlay/OnlinePlayScreen.cs | 13 ++++++----- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c60bff9e4c..5372f9227f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -46,6 +46,7 @@ using osu.Game.IO; using osu.Game.Localisation; using osu.Game.Online; using osu.Game.Online.Chat; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Music; @@ -58,6 +59,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Menu; +using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; @@ -643,6 +645,25 @@ namespace osu.Game }); } + /// + /// Join a multiplayer match immediately. + /// + /// The room to join. + /// The password to join the room, if any is given. + public void PresentMultiplayerMatch(Room room, string password) + { + PerformFromScreen(screen => + { + Multiplayer multiplayer = new Multiplayer(); + multiplayer.OnLoadComplete += _ => + { + multiplayer.Join(room, password); + }; + + screen.Push(multiplayer); + }); + } + /// /// Present a score's replay immediately. /// The user should have already requested this interactively. @@ -853,6 +874,7 @@ namespace osu.Game ScoreManager.PresentImport = items => PresentScore(items.First().Value); MultiplayerClient.PostNotification = n => Notifications.Post(n); + MultiplayerClient.InviteAccepted = PresentMultiplayerMatch; // make config aware of how to lookup skins for on-screen display purposes. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index 514b80b999..f74b5dd96e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; @@ -90,6 +91,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen(); + public void Join(Room room, string? password) + { + LoungeSubScreen.Join(room, password); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 37b50b4863..85eec7ac07 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -32,8 +32,9 @@ namespace osu.Game.Screens.OnlinePlay // while leases may be taken out by a subscreen. public override bool DisallowExternalBeatmapRulesetChanges => true; + protected LoungeSubScreen LoungeSubScreen; + private MultiplayerWaveContainer waves; - private LoungeSubScreen loungeSubScreen; private ScreenStack screenStack; [Cached(Type = typeof(IRoomManager))] @@ -89,7 +90,7 @@ namespace osu.Game.Screens.OnlinePlay screenStack.ScreenPushed += screenPushed; screenStack.ScreenExited += screenExited; - screenStack.Push(loungeSubScreen = CreateLounge()); + screenStack.Push(LoungeSubScreen = CreateLounge()); apiState.BindTo(API.State); apiState.BindValueChanged(onlineStateChanged, true); @@ -120,10 +121,10 @@ namespace osu.Game.Screens.OnlinePlay Mods.SetDefault(); - if (loungeSubScreen.IsCurrentScreen()) - loungeSubScreen.OnEntering(e); + if (LoungeSubScreen.IsCurrentScreen()) + LoungeSubScreen.OnEntering(e); else - loungeSubScreen.MakeCurrent(); + LoungeSubScreen.MakeCurrent(); } public override void OnResuming(ScreenTransitionEvent e) @@ -158,7 +159,7 @@ namespace osu.Game.Screens.OnlinePlay public override bool OnExiting(ScreenExitEvent e) { - while (screenStack.CurrentScreen != null && screenStack.CurrentScreen is not LoungeSubScreen) + while (screenStack.CurrentScreen != null && screenStack.CurrentScreen is not Lounge.LoungeSubScreen) { var subScreen = (Screen)screenStack.CurrentScreen; if (subScreen.IsLoaded && subScreen.OnExiting(e)) From 267d1ee7d442ef09bbdd5b7eb285c264d424ffb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Tue, 3 Oct 2023 22:08:14 +0200 Subject: [PATCH 09/37] Handle cases when player cannot be invited. --- .../Multiplayer/IMultiplayerRoomServer.cs | 3 +- .../Multiplayer/OnlineMultiplayerClient.cs | 29 +++++++++++++++++-- .../Multiplayer/UserBlockedException.cs | 26 +++++++++++++++++ .../Multiplayer/UserBlocksPMsException.cs | 26 +++++++++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/UserBlockedException.cs create mode 100644 osu.Game/Online/Multiplayer/UserBlocksPMsException.cs diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 64cd6df24d..e98570dc29 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -104,7 +104,8 @@ namespace osu.Game.Online.Multiplayer /// Invites a player to the current room. /// /// - /// + /// The user has blocked or has been blocked by the invited user. + /// The invited user does not accept private messages. Task InvitePlayer(int userId); } } diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 5b5741ef1c..08e82f2ad3 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Overlays.Notifications; namespace osu.Game.Online.Multiplayer { @@ -107,14 +108,36 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); } - public override Task InvitePlayer(int userId) + public override async Task InvitePlayer(int userId) { if (!IsConnected.Value) - return Task.CompletedTask; + return; Debug.Assert(connection != null); - return connection.InvokeAsync(nameof(IMultiplayerServer.InvitePlayer), userId); + try + { + await connection.InvokeAsync(nameof(IMultiplayerServer.InvitePlayer), userId).ConfigureAwait(false); + } + catch (HubException exception) + { + switch (exception.GetHubExceptionMessage()) + { + case UserBlockedException.MESSAGE: + PostNotification?.Invoke(new SimpleErrorNotification + { + Text = "User cannot be invited by someone they have blocked or are blocked by." + }); + break; + + case UserBlocksPMsException.MESSAGE: + PostNotification?.Invoke(new SimpleErrorNotification + { + Text = "User cannot be invited because they cannot receive private messages from people not on their friends list." + }); + break; + } + } } public override Task TransferHost(int userId) diff --git a/osu.Game/Online/Multiplayer/UserBlockedException.cs b/osu.Game/Online/Multiplayer/UserBlockedException.cs new file mode 100644 index 0000000000..363f878183 --- /dev/null +++ b/osu.Game/Online/Multiplayer/UserBlockedException.cs @@ -0,0 +1,26 @@ +// 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.Runtime.Serialization; +using Microsoft.AspNetCore.SignalR; + +namespace osu.Game.Online.Multiplayer +{ + [Serializable] + public class UserBlockedException : HubException + { + public const string MESSAGE = "User cannot be invited by someone they have blocked or are blocked by."; + + public UserBlockedException() + : + base(MESSAGE) + { + } + + protected UserBlockedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs new file mode 100644 index 0000000000..220a84cfe8 --- /dev/null +++ b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs @@ -0,0 +1,26 @@ +// 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.Runtime.Serialization; +using Microsoft.AspNetCore.SignalR; + +namespace osu.Game.Online.Multiplayer +{ + [Serializable] + public class UserBlocksPMsException : HubException + { + public const string MESSAGE = "User cannot be invited because they have disabled private messages."; + + public UserBlocksPMsException() + : + base(MESSAGE) + { + } + + protected UserBlocksPMsException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} From 32f69cd0ba58e9433214862751e830d33e70e748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Wed, 4 Oct 2023 00:20:07 +0200 Subject: [PATCH 10/37] Make `UserAvatarNotification.user` readonly --- osu.Game/Overlays/Notifications/UserAvatarNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/UserAvatarNotification.cs b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs index 191d63a76f..8f32c9d395 100644 --- a/osu.Game/Overlays/Notifications/UserAvatarNotification.cs +++ b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Notifications private TextFlowContainer? textDrawable; - private APIUser user; + private readonly APIUser user; public UserAvatarNotification(APIUser user, LocalisableString text) { From 5678d904615f6f300613904363c1ac8d72b26a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Wed, 4 Oct 2023 00:20:38 +0200 Subject: [PATCH 11/37] Reduce silliness of notification test case --- .../Visual/UserInterface/TestSceneNotificationOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 07bd722322..332053d71e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -549,7 +549,7 @@ namespace osu.Game.Tests.Visual.UserInterface var user = await userLookupCache.GetUserAsync(0).ConfigureAwait(true); if (user == null) return; - var n = new UserAvatarNotification(user, $"{user.Username} is telling you to NOT download Haitai!"); + var n = new UserAvatarNotification(user, $"{user.Username} invited you to a multiplayer match!"); notificationOverlay.Post(n); } From 5469d134cb0919b70fc6168d24d948d3133b67ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Wed, 4 Oct 2023 00:28:01 +0200 Subject: [PATCH 12/37] Add missing parameter description to docs. --- osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index e98570dc29..b7a608581c 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -103,7 +103,7 @@ namespace osu.Game.Online.Multiplayer /// /// Invites a player to the current room. /// - /// + /// The user to invite. /// The user has blocked or has been blocked by the invited user. /// The invited user does not accept private messages. Task InvitePlayer(int userId); From fe5177fa4fe29ea35b3fac74f50b330899dd7066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Wed, 4 Oct 2023 00:50:48 +0200 Subject: [PATCH 13/37] Remove unused import --- osu.Game/Overlays/Notifications/UserAvatarNotification.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/UserAvatarNotification.cs b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs index 8f32c9d395..af881a5008 100644 --- a/osu.Game/Overlays/Notifications/UserAvatarNotification.cs +++ b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; From bfeafd6f70bcd0832c144b3bf8d7cbd75c148285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Wed, 4 Oct 2023 08:30:48 +0200 Subject: [PATCH 14/37] Fix formatting --- osu.Game/Online/Multiplayer/UserBlockedException.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/UserBlockedException.cs b/osu.Game/Online/Multiplayer/UserBlockedException.cs index 363f878183..f2b69411c7 100644 --- a/osu.Game/Online/Multiplayer/UserBlockedException.cs +++ b/osu.Game/Online/Multiplayer/UserBlockedException.cs @@ -13,8 +13,7 @@ namespace osu.Game.Online.Multiplayer public const string MESSAGE = "User cannot be invited by someone they have blocked or are blocked by."; public UserBlockedException() - : - base(MESSAGE) + : base(MESSAGE) { } From 74ed3bc4ff8f8206188ec115c319222b0b31a99c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Wed, 4 Oct 2023 08:31:50 +0200 Subject: [PATCH 15/37] Rename `loadRoom` to `lookupRoom` --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 61c2fa495a..8b4c38a152 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -448,7 +448,7 @@ namespace osu.Game.Online.Multiplayer async Task IMultiplayerClient.Invited(int invitedBy, long roomID, string password) { var loadUserTask = userLookupCache.GetUserAsync(invitedBy); - var loadRoomTask = loadRoom(roomID); + var loadRoomTask = lookupRoom(roomID); await Task.WhenAll(loadUserTask, loadRoomTask).ConfigureAwait(false); @@ -472,7 +472,7 @@ namespace osu.Game.Online.Multiplayer }); } - private Task loadRoom(long id) + private Task lookupRoom(long id) { return Task.Run(() => { From 0726ccb9887a9be8c653cf61de6fbf0a4c31bbff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Wed, 4 Oct 2023 08:32:18 +0200 Subject: [PATCH 16/37] Fix formatting --- osu.Game/Online/Multiplayer/UserBlocksPMsException.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs index 220a84cfe8..0fd4e1f0c1 100644 --- a/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs +++ b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs @@ -13,8 +13,7 @@ namespace osu.Game.Online.Multiplayer public const string MESSAGE = "User cannot be invited because they have disabled private messages."; public UserBlocksPMsException() - : - base(MESSAGE) + : base(MESSAGE) { } From 6bd51b32b47919a69a096c09ff8eac76c8bbe477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Wed, 4 Oct 2023 08:35:45 +0200 Subject: [PATCH 17/37] Make resolved `multiplayerClient` field nullable --- osu.Game/Users/UserPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 29a6c65555..98b306e264 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -64,7 +64,7 @@ namespace osu.Game.Users protected OsuColour Colours { get; private set; } = null!; [Resolved] - private MultiplayerClient multiplayerClient { get; set; } = null!; + private MultiplayerClient? multiplayerClient { get; set; } [BackgroundDependencyLoader] private void load() @@ -124,7 +124,7 @@ namespace osu.Game.Users if ( User.IsOnline && - multiplayerClient.Room != null && + multiplayerClient?.Room != null && multiplayerClient.Room.Users.All(u => u.UserID != User.Id) ) { From 6d97f89399ec328b178f8f0561d0479650f2c86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Wed, 4 Oct 2023 08:37:22 +0200 Subject: [PATCH 18/37] Rename `OnlinePlayScreen.LoungeSubScreen` to `Lounge` --- .../Screens/OnlinePlay/Multiplayer/Multiplayer.cs | 2 +- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index f74b5dd96e..f899b24d30 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public void Join(Room room, string? password) { - LoungeSubScreen.Join(room, password); + Lounge.Join(room, password); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 85eec7ac07..57e388f016 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay // while leases may be taken out by a subscreen. public override bool DisallowExternalBeatmapRulesetChanges => true; - protected LoungeSubScreen LoungeSubScreen; + protected LoungeSubScreen Lounge; private MultiplayerWaveContainer waves; private ScreenStack screenStack; @@ -90,7 +90,7 @@ namespace osu.Game.Screens.OnlinePlay screenStack.ScreenPushed += screenPushed; screenStack.ScreenExited += screenExited; - screenStack.Push(LoungeSubScreen = CreateLounge()); + screenStack.Push(Lounge = CreateLounge()); apiState.BindTo(API.State); apiState.BindValueChanged(onlineStateChanged, true); @@ -121,10 +121,10 @@ namespace osu.Game.Screens.OnlinePlay Mods.SetDefault(); - if (LoungeSubScreen.IsCurrentScreen()) - LoungeSubScreen.OnEntering(e); + if (Lounge.IsCurrentScreen()) + Lounge.OnEntering(e); else - LoungeSubScreen.MakeCurrent(); + Lounge.MakeCurrent(); } public override void OnResuming(ScreenTransitionEvent e) @@ -159,7 +159,7 @@ namespace osu.Game.Screens.OnlinePlay public override bool OnExiting(ScreenExitEvent e) { - while (screenStack.CurrentScreen != null && screenStack.CurrentScreen is not Lounge.LoungeSubScreen) + while (screenStack.CurrentScreen != null && screenStack.CurrentScreen is not LoungeSubScreen) { var subScreen = (Screen)screenStack.CurrentScreen; if (subScreen.IsLoaded && subScreen.OnExiting(e)) From 3023e4419607de57f8605a1c27f3a6274a3b45f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 15:52:48 +0900 Subject: [PATCH 19/37] Remove unused using statements --- .../Visual/UserInterface/TestSceneNotificationOverlay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 332053d71e..114c070acc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -14,7 +12,6 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Database; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; From 55a9de034d9d79d8b9d9818e55ea914f8ab29e59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 16:00:23 +0900 Subject: [PATCH 20/37] Change `NotificationOverlay` type based logic to not require specifying every type of notification --- osu.Game/Overlays/NotificationOverlay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 67cf868fb0..dd4924d07a 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -113,7 +113,8 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, Children = new[] { - new NotificationSection(AccountsStrings.NotificationsTitle, new[] { typeof(SimpleNotification), typeof(UserAvatarNotification) }), + // The main section adds as a catch-all for notifications which don't group into other sections. + new NotificationSection(AccountsStrings.NotificationsTitle, new[] { typeof(Notification) }), new NotificationSection(NotificationsStrings.RunningTasks, new[] { typeof(ProgressNotification) }), } } @@ -205,7 +206,7 @@ namespace osu.Game.Overlays var ourType = notification.GetType(); int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; - var section = sections.Children.First(s => s.AcceptedNotificationTypes.Any(accept => accept.IsAssignableFrom(ourType))); + var section = sections.Children.Last(s => s.AcceptedNotificationTypes.Any(accept => accept.IsAssignableFrom(ourType))); section.Add(notification, depth); From e6103fea95ed8bffde81ca16bfd4da2631c91ad4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 16:00:36 +0900 Subject: [PATCH 21/37] Fix `async` usage in `TestSceneNotificationOverlay` --- .../Visual/UserInterface/TestSceneNotificationOverlay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 114c070acc..c584c7dba0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -541,9 +542,9 @@ namespace osu.Game.Tests.Visual.UserInterface progressingNotifications.Add(n); } - private async void sendUserNotification() + private void sendUserNotification() { - var user = await userLookupCache.GetUserAsync(0).ConfigureAwait(true); + var user = userLookupCache.GetUserAsync(0).GetResultSafely(); if (user == null) return; var n = new UserAvatarNotification(user, $"{user.Username} invited you to a multiplayer match!"); From aee8ba789c1f4f022eaa3abf90173afce1fcc591 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 16:02:19 +0900 Subject: [PATCH 22/37] Tidy up `UserAvatarNotification` implementation --- .../Overlays/Notifications/Notification.cs | 4 +- .../Notifications/UserAvatarNotification.cs | 39 ++++++++----------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 8cdc373417..d619d1d3c3 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -53,6 +53,8 @@ namespace osu.Game.Overlays.Notifications public virtual string PopInSampleName => "UI/notification-default"; public virtual string PopOutSampleName => "UI/overlay-pop-out"; + protected const float CORNER_RADIUS = 6; + protected NotificationLight Light; protected Container IconContent; @@ -128,7 +130,7 @@ namespace osu.Game.Overlays.Notifications AutoSizeAxes = Axes.Y, }.WithChild(MainContent = new Container { - CornerRadius = 6, + CornerRadius = CORNER_RADIUS, Masking = true, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/Notifications/UserAvatarNotification.cs b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs index af881a5008..04766f7743 100644 --- a/osu.Game/Overlays/Notifications/UserAvatarNotification.cs +++ b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs @@ -39,39 +39,34 @@ namespace osu.Game.Overlays.Notifications Text = text; } - private DrawableAvatar? avatar; - protected override IconUsage CloseButtonIcon => FontAwesome.Solid.Times; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - IconContent.Masking = true; - - // Workaround for the corner radius on parent's mask breaking if we add masking to IconContent - IconContent.CornerRadius = 6; - - IconContent.AddRange(new Drawable[] - { - new Box() - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background5, - }, - }); - - avatar = new DrawableAvatar(user) - { - FillMode = FillMode.Fill, - }; - LoadComponentAsync(avatar, IconContent.Add); - Content.Add(textDrawable = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 14, weight: FontWeight.Medium)) { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Text = text }); + + IconContent.Masking = true; + IconContent.CornerRadius = CORNER_RADIUS; + + IconContent.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + }); + + LoadComponentAsync(new DrawableAvatar(user) + { + FillMode = FillMode.Fill, + }, IconContent.Add); } } } From 20f32e2025a3d992bdd1b4b75eeb9360ea9932fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 16:05:48 +0900 Subject: [PATCH 23/37] Add light colouring for user notifications and adjust lighting slightly --- osu.Game/Overlays/Notifications/Notification.cs | 5 ++--- osu.Game/Overlays/Notifications/UserAvatarNotification.cs | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index d619d1d3c3..8108861dca 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -473,10 +473,9 @@ namespace osu.Game.Overlays.Notifications base.Colour = value; pulsateLayer.EdgeEffect = new EdgeEffectParameters { - Colour = ((Color4)value).Opacity(0.5f), //todo: avoid cast + Colour = ((Color4)value).Opacity(0.18f), Type = EdgeEffectType.Glow, - Radius = 12, - Roundness = 12, + Radius = 14, }; } } diff --git a/osu.Game/Overlays/Notifications/UserAvatarNotification.cs b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs index 04766f7743..5a9241a2a1 100644 --- a/osu.Game/Overlays/Notifications/UserAvatarNotification.cs +++ b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs @@ -42,8 +42,10 @@ namespace osu.Game.Overlays.Notifications protected override IconUsage CloseButtonIcon => FontAwesome.Solid.Times; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OsuColour colours, OverlayColourProvider colourProvider) { + Light.Colour = colours.Orange2; + Content.Add(textDrawable = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 14, weight: FontWeight.Medium)) { AutoSizeAxes = Axes.Y, From a512ef56379637181bd0eab2393295d5608d9726 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 17:41:31 +0900 Subject: [PATCH 24/37] Add exceptions to online state handling --- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 5 ++++- osu.Game/Users/UserPanel.cs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 5047992c8b..02f0a6e80d 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -119,7 +119,7 @@ namespace osu.Game.Overlays.Dashboard { users.GetUserAsync(userId).ContinueWith(task => { - var user = task.GetResultSafely(); + APIUser user = task.GetResultSafely(); if (user == null) return; @@ -130,6 +130,9 @@ namespace osu.Game.Overlays.Dashboard if (!playingUsers.Contains(user.Id)) return; + // TODO: remove this once online state is being updated more correctly. + user.IsOnline = true; + userFlow.Add(createUserPanel(user)); }); }); diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 98b306e264..273faf9bd1 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -123,7 +123,8 @@ namespace osu.Game.Users } if ( - User.IsOnline && + // TODO: uncomment this once lazer / osu-web is updating online states + // User.IsOnline && multiplayerClient?.Room != null && multiplayerClient.Room.Users.All(u => u.UserID != User.Id) ) From 94d7a65e4062578ac13b96da10e9dc3c70b67899 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 17:42:02 +0900 Subject: [PATCH 25/37] Schedule `Join` operations rather than using `OnLoadComplete` for added safety --- osu.Game/OsuGame.cs | 11 +++--- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 37 ++++++++++--------- .../OnlinePlay/Multiplayer/Multiplayer.cs | 5 +-- .../Screens/OnlinePlay/OnlinePlayScreen.cs | 4 +- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5372f9227f..ecf27cd116 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -654,14 +654,13 @@ namespace osu.Game { PerformFromScreen(screen => { - Multiplayer multiplayer = new Multiplayer(); - multiplayer.OnLoadComplete += _ => - { - multiplayer.Join(room, password); - }; + if (!(screen is Multiplayer multiplayer)) + screen.Push(multiplayer = new Multiplayer()); - screen.Push(multiplayer); + multiplayer.Join(room, password); }); + // TODO: We should really be able to use `validScreens: new[] { typeof(Multiplayer) }` here + // but `PerformFromScreen` doesn't understand nested stacks. } /// diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index fc4a5357c6..04ea59621d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -297,26 +297,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge popoverContainer.HidePopover(); } - public virtual void Join(Room room, string password, Action onSuccess = null, Action onFailure = null) => Schedule(() => + public void Join(Room room, string password, Action onSuccess = null, Action onFailure = null) { - if (joiningRoomOperation != null) - return; - - joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); - - RoomManager?.JoinRoom(room, password, _ => + Schedule(() => { - Open(room); - joiningRoomOperation?.Dispose(); - joiningRoomOperation = null; - onSuccess?.Invoke(room); - }, error => - { - joiningRoomOperation?.Dispose(); - joiningRoomOperation = null; - onFailure?.Invoke(error); + if (joiningRoomOperation != null) + return; + + joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); + + RoomManager?.JoinRoom(room, password, _ => + { + Open(room); + joiningRoomOperation?.Dispose(); + joiningRoomOperation = null; + onSuccess?.Invoke(room); + }, error => + { + joiningRoomOperation?.Dispose(); + joiningRoomOperation = null; + onFailure?.Invoke(error); + }); }); - }); + } /// /// Copies a room and opens it as a fresh (not-yet-created) one. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index f899b24d30..edf5ce276a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -91,10 +91,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen(); - public void Join(Room room, string? password) - { - Lounge.Join(room, password); - } + public void Join(Room room, string? password) => Schedule(() => Lounge.Join(room, password)); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 57e388f016..88bbff6b92 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -26,6 +26,8 @@ namespace osu.Game.Screens.OnlinePlay [Cached] protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + public IScreen CurrentSubScreen => screenStack.CurrentScreen; + public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true; // this is required due to PlayerLoader eventually being pushed to the main stack @@ -225,8 +227,6 @@ namespace osu.Game.Screens.OnlinePlay ((IBindable)Activity).BindTo(newOsuScreen.Activity); } - public IScreen CurrentSubScreen => screenStack.CurrentScreen; - protected abstract string ScreenTitle { get; } protected virtual RoomManager CreateRoomManager() => new RoomManager(); From cde4fad61027010d411e444b25ac0d111db16dce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 17:55:14 +0900 Subject: [PATCH 26/37] Simplify `async` lookup logic in `Invited` handling --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 8b4c38a152..6ba7953c8d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -11,7 +11,6 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Database; @@ -447,13 +446,8 @@ namespace osu.Game.Online.Multiplayer async Task IMultiplayerClient.Invited(int invitedBy, long roomID, string password) { - var loadUserTask = userLookupCache.GetUserAsync(invitedBy); - var loadRoomTask = lookupRoom(roomID); - - await Task.WhenAll(loadUserTask, loadRoomTask).ConfigureAwait(false); - - APIUser? apiUser = loadUserTask.GetResultSafely(); - Room? apiRoom = loadRoomTask.GetResultSafely(); + APIUser? apiUser = await userLookupCache.GetUserAsync(invitedBy); + Room? apiRoom = await lookupRoom(roomID); if (apiUser == null || apiRoom == null) return; From a1a9bb75b79097e3abace2fa2e533ef031c73848 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 17:55:58 +0900 Subject: [PATCH 27/37] Remove unnecessary schedule logic --- .../Online/Multiplayer/MultiplayerClient.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 6ba7953c8d..3f4a26bf6c 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -451,19 +451,16 @@ namespace osu.Game.Online.Multiplayer if (apiUser == null || apiRoom == null) return; - Scheduler.Add(() => - { - PostNotification?.Invoke( - new UserAvatarNotification(apiUser, $"{apiUser.Username} invited you to a multiplayer match:\"{apiRoom.Name}\"!") + PostNotification?.Invoke( + new UserAvatarNotification(apiUser, $"{apiUser.Username} invited you to a multiplayer match:\"{apiRoom.Name}\"!") + { + Activated = () => { - Activated = () => - { - InviteAccepted?.Invoke(apiRoom, password); - return true; - } + InviteAccepted?.Invoke(apiRoom, password); + return true; } - ); - }); + } + ); } private Task lookupRoom(long id) From d2aa601912c3cd75fd8d28491e06584b493b0294 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 17:59:08 +0900 Subject: [PATCH 28/37] Allow localisation of the invite notification --- osu.Game/Localisation/NotificationsStrings.cs | 5 +++++ osu.Game/Online/Multiplayer/MultiplayerClient.cs | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index 53687f2b28..9cb286bc91 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -93,6 +93,11 @@ Please try changing your audio device to a working setting."); /// public static LocalisableString YourNameWasMentioned(string username) => new TranslatableString(getKey(@"your_name_was_mentioned"), @"Your name was mentioned in chat by '{0}'. Click to find out why!", username); + /// + /// "{0} invited you to the multiplayer match "{1}"! Click to join." + /// + public static LocalisableString InvitedYouToTheMultiplayer(string username, string roomName) => new TranslatableString(getKey(@"invited_you_to_the_multiplayer"), @"{0} invited you to the multiplayer match ""{1}""! Click to join.", username, roomName); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 3f4a26bf6c..e8653b86be 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -23,6 +23,7 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Utils; +using osu.Game.Localisation; namespace osu.Game.Online.Multiplayer { @@ -452,7 +453,7 @@ namespace osu.Game.Online.Multiplayer if (apiUser == null || apiRoom == null) return; PostNotification?.Invoke( - new UserAvatarNotification(apiUser, $"{apiUser.Username} invited you to a multiplayer match:\"{apiRoom.Name}\"!") + new UserAvatarNotification(apiUser, NotificationsStrings.InvitedYouToTheMultiplayer(apiUser.Username, apiRoom.Name.Value)) { Activated = () => { From 361d70f68affb6e422edc9cc521be89d66a0a66c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 17:59:47 +0900 Subject: [PATCH 29/37] Rename callback to match standards --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 4 ++-- osu.Game/OsuGame.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index e8653b86be..978eee505c 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -31,7 +31,7 @@ namespace osu.Game.Online.Multiplayer { public Action? PostNotification { protected get; set; } - public Action? InviteAccepted { protected get; set; } + public Action? PresentMatch { protected get; set; } /// /// Invoked when any change occurs to the multiplayer room. @@ -457,7 +457,7 @@ namespace osu.Game.Online.Multiplayer { Activated = () => { - InviteAccepted?.Invoke(apiRoom, password); + PresentMatch?.Invoke(apiRoom, password); return true; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ecf27cd116..885077a8e8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -873,7 +873,7 @@ namespace osu.Game ScoreManager.PresentImport = items => PresentScore(items.First().Value); MultiplayerClient.PostNotification = n => Notifications.Post(n); - MultiplayerClient.InviteAccepted = PresentMultiplayerMatch; + MultiplayerClient.PresentMatch = PresentMultiplayerMatch; // make config aware of how to lookup skins for on-screen display purposes. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. From 5f62c225bf7e7b03950aa931962a2fc168216ffe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 18:07:03 +0900 Subject: [PATCH 30/37] Ad localisation (and adjust messaging) of invite failures --- osu.Game/Localisation/OnlinePlayStrings.cs | 10 ++++++++++ .../Online/Multiplayer/OnlineMultiplayerClient.cs | 11 +++-------- osu.Game/Online/Multiplayer/UserBlockedException.cs | 2 +- osu.Game/Online/Multiplayer/UserBlocksPMsException.cs | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Localisation/OnlinePlayStrings.cs b/osu.Game/Localisation/OnlinePlayStrings.cs index 1853cb753a..1918519d36 100644 --- a/osu.Game/Localisation/OnlinePlayStrings.cs +++ b/osu.Game/Localisation/OnlinePlayStrings.cs @@ -14,6 +14,16 @@ namespace osu.Game.Localisation /// public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"Playlist durations longer than 2 weeks require an active osu!supporter tag."); + /// + /// "Can't invite this user as you have blocked them or they have blocked you." + /// + public static LocalisableString InviteFailedUserBlocked => new TranslatableString(getKey(@"cant_invite_this_user_as"), @"Can't invite this user as you have blocked them or they have blocked you."); + + /// + /// "Can't invite this user as they have opted out of non-friend communications." + /// + public static LocalisableString InviteFailedUserOptOut => new TranslatableString(getKey(@"cant_invite_this_user_as1"), @"Can't invite this user as they have opted out of non-friend communications."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 08e82f2ad3..20ec030eac 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -13,6 +13,7 @@ using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Overlays.Notifications; +using osu.Game.Localisation; namespace osu.Game.Online.Multiplayer { @@ -124,17 +125,11 @@ namespace osu.Game.Online.Multiplayer switch (exception.GetHubExceptionMessage()) { case UserBlockedException.MESSAGE: - PostNotification?.Invoke(new SimpleErrorNotification - { - Text = "User cannot be invited by someone they have blocked or are blocked by." - }); + PostNotification?.Invoke(new SimpleErrorNotification { Text = OnlinePlayStrings.InviteFailedUserBlocked }); break; case UserBlocksPMsException.MESSAGE: - PostNotification?.Invoke(new SimpleErrorNotification - { - Text = "User cannot be invited because they cannot receive private messages from people not on their friends list." - }); + PostNotification?.Invoke(new SimpleErrorNotification { Text = OnlinePlayStrings.InviteFailedUserOptOut }); break; } } diff --git a/osu.Game/Online/Multiplayer/UserBlockedException.cs b/osu.Game/Online/Multiplayer/UserBlockedException.cs index f2b69411c7..e964b13c75 100644 --- a/osu.Game/Online/Multiplayer/UserBlockedException.cs +++ b/osu.Game/Online/Multiplayer/UserBlockedException.cs @@ -10,7 +10,7 @@ namespace osu.Game.Online.Multiplayer [Serializable] public class UserBlockedException : HubException { - public const string MESSAGE = "User cannot be invited by someone they have blocked or are blocked by."; + public const string MESSAGE = @"Cannot perform action due to user being blocked."; public UserBlockedException() : base(MESSAGE) diff --git a/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs index 0fd4e1f0c1..14ed6fc212 100644 --- a/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs +++ b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs @@ -10,7 +10,7 @@ namespace osu.Game.Online.Multiplayer [Serializable] public class UserBlocksPMsException : HubException { - public const string MESSAGE = "User cannot be invited because they have disabled private messages."; + public const string MESSAGE = "Cannot perform action because user has disabled non-friend communications."; public UserBlocksPMsException() : base(MESSAGE) From a591af2b9754af0cd09ad5b1c1e28b2f07235c19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 18:11:04 +0900 Subject: [PATCH 31/37] Fix too many tasks in `lookupRoom` method --- .../Online/Multiplayer/MultiplayerClient.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 978eee505c..dd131ee2e6 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -448,7 +448,7 @@ namespace osu.Game.Online.Multiplayer async Task IMultiplayerClient.Invited(int invitedBy, long roomID, string password) { APIUser? apiUser = await userLookupCache.GetUserAsync(invitedBy); - Room? apiRoom = await lookupRoom(roomID); + Room? apiRoom = await getRoomAsync(roomID); if (apiUser == null || apiRoom == null) return; @@ -462,23 +462,19 @@ namespace osu.Game.Online.Multiplayer } } ); - } - private Task lookupRoom(long id) - { - return Task.Run(() => + Task getRoomAsync(long id) { - var t = new TaskCompletionSource(); + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + var request = new GetRoomRequest(id); - - request.Success += room => t.TrySetResult(room); - - request.Failure += e => t.TrySetResult(null); + request.Success += room => taskCompletionSource.TrySetResult(room); + request.Failure += _ => taskCompletionSource.TrySetResult(null); API.Queue(request); - return t.Task; - }); + return taskCompletionSource.Task; + } } private void addUserToAPIRoom(MultiplayerRoomUser user) From e48c4fa430ca938b9c3648f906ac36525d770a32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 18:15:15 +0900 Subject: [PATCH 32/37] Add missing `ConfigureAwait` calls --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index dd131ee2e6..515a0dda08 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -447,8 +447,8 @@ namespace osu.Game.Online.Multiplayer async Task IMultiplayerClient.Invited(int invitedBy, long roomID, string password) { - APIUser? apiUser = await userLookupCache.GetUserAsync(invitedBy); - Room? apiRoom = await getRoomAsync(roomID); + APIUser? apiUser = await userLookupCache.GetUserAsync(invitedBy).ConfigureAwait(false); + Room? apiRoom = await getRoomAsync(roomID).ConfigureAwait(false); if (apiUser == null || apiRoom == null) return; From fc1287b4c695d821465984f0a2835e17e6ad0f02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 18:25:20 +0900 Subject: [PATCH 33/37] Reword xmldoc on invite method to better describe what is happening --- osu.Game/Online/Multiplayer/IMultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index ba9e8a237c..327fb0d76a 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -43,7 +43,7 @@ namespace osu.Game.Online.Multiplayer Task UserKicked(MultiplayerRoomUser user); /// - /// Signals that a user has been invited into a multiplayer room. + /// Signals that the local user has been invited into a multiplayer room. /// /// Id of user that invited the player. /// Id of the room the user got invited to. From 1ce268be3fe04c62b0bdae4ed100aa07011d500e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Oct 2023 18:58:42 +0900 Subject: [PATCH 34/37] Update some packages to match `osu.Server.Spectator` --- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- osu.Game/osu.Game.csproj | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index febe353b81..5de21a68d0 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -7,7 +7,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 69675e9c1b..f0a4b38c03 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,13 +21,13 @@ - + - - - - - + + + + + @@ -38,8 +38,8 @@ - - + + From 6c8490bc7ef47b521d3c90c59573c82ecc2f18bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 12 Oct 2023 20:39:04 +0200 Subject: [PATCH 35/37] Revert changes to `LoungeSubScreen.Join()` - `virtual` modifier was used in mocking. - The spacing change revert is just mostly to keep it out of the final diff. --- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 04ea59621d..fc4a5357c6 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -297,29 +297,26 @@ namespace osu.Game.Screens.OnlinePlay.Lounge popoverContainer.HidePopover(); } - public void Join(Room room, string password, Action onSuccess = null, Action onFailure = null) + public virtual void Join(Room room, string password, Action onSuccess = null, Action onFailure = null) => Schedule(() => { - Schedule(() => + if (joiningRoomOperation != null) + return; + + joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); + + RoomManager?.JoinRoom(room, password, _ => { - if (joiningRoomOperation != null) - return; - - joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); - - RoomManager?.JoinRoom(room, password, _ => - { - Open(room); - joiningRoomOperation?.Dispose(); - joiningRoomOperation = null; - onSuccess?.Invoke(room); - }, error => - { - joiningRoomOperation?.Dispose(); - joiningRoomOperation = null; - onFailure?.Invoke(error); - }); + Open(room); + joiningRoomOperation?.Dispose(); + joiningRoomOperation = null; + onSuccess?.Invoke(room); + }, error => + { + joiningRoomOperation?.Dispose(); + joiningRoomOperation = null; + onFailure?.Invoke(error); }); - } + }); /// /// Copies a room and opens it as a fresh (not-yet-created) one. From 79cec7707d46b4381c59e5d51d5d258f779d45a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Oct 2023 09:56:46 +0200 Subject: [PATCH 36/37] Privatise setter --- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 88bbff6b92..f652e88f5a 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay // while leases may be taken out by a subscreen. public override bool DisallowExternalBeatmapRulesetChanges => true; - protected LoungeSubScreen Lounge; + protected LoungeSubScreen Lounge { get; private set; } private MultiplayerWaveContainer waves; private ScreenStack screenStack; From e04a57d67f68db5b45a9ab5767bc0ed57f0b1a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Oct 2023 10:31:00 +0200 Subject: [PATCH 37/37] Use less dodgy method of specifying allowable notification types --- osu.Game/Overlays/NotificationOverlay.cs | 5 +++-- osu.Game/Overlays/Notifications/NotificationSection.cs | 10 +++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index dd4924d07a..81233b4343 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -114,7 +114,7 @@ namespace osu.Game.Overlays Children = new[] { // The main section adds as a catch-all for notifications which don't group into other sections. - new NotificationSection(AccountsStrings.NotificationsTitle, new[] { typeof(Notification) }), + new NotificationSection(AccountsStrings.NotificationsTitle), new NotificationSection(NotificationsStrings.RunningTasks, new[] { typeof(ProgressNotification) }), } } @@ -206,7 +206,8 @@ namespace osu.Game.Overlays var ourType = notification.GetType(); int depth = notification.DisplayOnTop ? -runningDepth : runningDepth; - var section = sections.Children.Last(s => s.AcceptedNotificationTypes.Any(accept => accept.IsAssignableFrom(ourType))); + var section = sections.Children.FirstOrDefault(s => s.AcceptedNotificationTypes?.Any(accept => accept.IsAssignableFrom(ourType)) == true) + ?? sections.First(); section.Add(notification, depth); diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 10c2900d63..895f13ee9c 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -37,13 +37,17 @@ namespace osu.Game.Overlays.Notifications notifications.Insert((int)position, notification); } - public IEnumerable AcceptedNotificationTypes { get; } + /// + /// Enumerable of notification types accepted in this section. + /// If , the section accepts any and all notifications. + /// + public IEnumerable? AcceptedNotificationTypes { get; } private readonly LocalisableString titleText; - public NotificationSection(LocalisableString title, IEnumerable acceptedNotificationTypes) + public NotificationSection(LocalisableString title, IEnumerable? acceptedNotificationTypes = null) { - AcceptedNotificationTypes = acceptedNotificationTypes.ToArray(); + AcceptedNotificationTypes = acceptedNotificationTypes?.ToArray(); titleText = title; }