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.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 4d3ae079e3..c584c7dba0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -5,11 +5,13 @@ 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; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Database; using osu.Game.Graphics.Sprites; using osu.Game.Online.Multiplayer; using osu.Game.Overlays; @@ -31,6 +33,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 +64,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 +542,16 @@ namespace osu.Game.Tests.Visual.UserInterface progressingNotifications.Add(n); } + private void sendUserNotification() + { + var user = userLookupCache.GetUserAsync(0).GetResultSafely(); + if (user == null) return; + + var n = new UserAvatarNotification(user, $"{user.Username} invited you to a multiplayer match!"); + + notificationOverlay.Post(n); + } + private void sendUploadProgress() { var n = new ProgressNotification 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}"; } } diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index adbd7a354b..fb3dab032d 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); + /// /// "You do not have the beatmap for this replay." /// 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/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 995bac1af5..327fb0d76a 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -42,6 +42,14 @@ namespace osu.Game.Online.Multiplayer /// The user. Task UserKicked(MultiplayerRoomUser user); + /// + /// 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. + /// 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/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index b7a5faf7c9..b7a608581c 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -99,5 +99,13 @@ namespace osu.Game.Online.Multiplayer /// /// The item to remove. Task RemovePlaylistItem(long playlistItemId); + + /// + /// 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); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 5716b7ad3b..515a0dda08 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 { @@ -30,6 +31,8 @@ namespace osu.Game.Online.Multiplayer { public Action? PostNotification { protected get; set; } + public Action? PresentMatch { protected get; set; } + /// /// Invoked when any change occurs to the multiplayer room. /// @@ -260,6 +263,8 @@ namespace osu.Game.Online.Multiplayer protected abstract Task LeaveRoomInternal(); + public abstract Task InvitePlayer(int userId); + /// /// Change the current settings. /// @@ -440,6 +445,38 @@ namespace osu.Game.Online.Multiplayer return handleUserLeft(user, UserKicked); } + async Task IMultiplayerClient.Invited(int invitedBy, long roomID, string password) + { + APIUser? apiUser = await userLookupCache.GetUserAsync(invitedBy).ConfigureAwait(false); + Room? apiRoom = await getRoomAsync(roomID).ConfigureAwait(false); + + if (apiUser == null || apiRoom == null) return; + + PostNotification?.Invoke( + new UserAvatarNotification(apiUser, NotificationsStrings.InvitedYouToTheMultiplayer(apiUser.Username, apiRoom.Name.Value)) + { + Activated = () => + { + PresentMatch?.Invoke(apiRoom, password); + return true; + } + } + ); + + Task getRoomAsync(long id) + { + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + + var request = new GetRoomRequest(id); + request.Success += room => taskCompletionSource.TrySetResult(room); + request.Failure += _ => taskCompletionSource.TrySetResult(null); + + API.Queue(request); + + return taskCompletionSource.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 8ff0ce4065..20ec030eac 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -12,6 +12,8 @@ using osu.Framework.Allocation; 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 { @@ -50,6 +52,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); @@ -106,6 +109,32 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); } + public override async Task InvitePlayer(int userId) + { + if (!IsConnected.Value) + return; + + Debug.Assert(connection != null); + + try + { + await connection.InvokeAsync(nameof(IMultiplayerServer.InvitePlayer), userId).ConfigureAwait(false); + } + catch (HubException exception) + { + switch (exception.GetHubExceptionMessage()) + { + case UserBlockedException.MESSAGE: + PostNotification?.Invoke(new SimpleErrorNotification { Text = OnlinePlayStrings.InviteFailedUserBlocked }); + break; + + case UserBlocksPMsException.MESSAGE: + PostNotification?.Invoke(new SimpleErrorNotification { Text = OnlinePlayStrings.InviteFailedUserOptOut }); + break; + } + } + } + public override Task TransferHost(int userId) { if (!IsConnected.Value) diff --git a/osu.Game/Online/Multiplayer/UserBlockedException.cs b/osu.Game/Online/Multiplayer/UserBlockedException.cs new file mode 100644 index 0000000000..e964b13c75 --- /dev/null +++ b/osu.Game/Online/Multiplayer/UserBlockedException.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Runtime.Serialization; +using Microsoft.AspNetCore.SignalR; + +namespace osu.Game.Online.Multiplayer +{ + [Serializable] + public class UserBlockedException : HubException + { + public const string MESSAGE = @"Cannot perform action due to user being blocked."; + + 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..14ed6fc212 --- /dev/null +++ b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Runtime.Serialization; +using Microsoft.AspNetCore.SignalR; + +namespace osu.Game.Online.Multiplayer +{ + [Serializable] + public class UserBlocksPMsException : HubException + { + public const string MESSAGE = "Cannot perform action because user has disabled non-friend communications."; + + public UserBlocksPMsException() + : base(MESSAGE) + { + } + + protected UserBlocksPMsException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c60bff9e4c..885077a8e8 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,24 @@ 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 => + { + if (!(screen is Multiplayer multiplayer)) + screen.Push(multiplayer = new 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. + } + /// /// Present a score's replay immediately. /// The user should have already requested this interactively. @@ -853,6 +873,7 @@ namespace osu.Game ScoreManager.PresentImport = items => PresentScore(items.First().Value); MultiplayerClient.PostNotification = n => Notifications.Post(n); + 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. 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/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 6e0ea23dd1..81233b4343 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) }), + // The main section adds as a catch-all for notifications which don't group into other sections. + new NotificationSection(AccountsStrings.NotificationsTitle), new NotificationSection(NotificationsStrings.RunningTasks, new[] { typeof(ProgressNotification) }), } } @@ -205,7 +206,8 @@ 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.FirstOrDefault(s => s.AcceptedNotificationTypes?.Any(accept => accept.IsAssignableFrom(ourType)) == true) + ?? sections.First(); section.Add(notification, depth); diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 805604c274..d48524d8b0 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, @@ -473,10 +475,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/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; } diff --git a/osu.Game/Overlays/Notifications/UserAvatarNotification.cs b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs new file mode 100644 index 0000000000..5a9241a2a1 --- /dev/null +++ b/osu.Game/Overlays/Notifications/UserAvatarNotification.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.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 readonly APIUser user; + + public UserAvatarNotification(APIUser user, LocalisableString text) + { + this.user = user; + Text = text; + } + + protected override IconUsage CloseButtonIcon => FontAwesome.Solid.Times; + + [BackgroundDependencyLoader] + 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, + 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); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index 514b80b999..edf5ce276a 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,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen(); + public void Join(Room room, string? password) => Schedule(() => Lounge.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..f652e88f5a 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -26,14 +26,17 @@ 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 // while leases may be taken out by a subscreen. public override bool DisallowExternalBeatmapRulesetChanges => true; + protected LoungeSubScreen Lounge { get; private set; } + private MultiplayerWaveContainer waves; - private LoungeSubScreen loungeSubScreen; private ScreenStack screenStack; [Cached(Type = typeof(IRoomManager))] @@ -89,7 +92,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); @@ -120,10 +123,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) @@ -224,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(); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 9dc24eff69..6007c7c076 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); diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index e2dc511391..273faf9bd1 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; } + [BackgroundDependencyLoader] private void load() { @@ -117,6 +122,16 @@ namespace osu.Game.Users })); } + if ( + // 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) + ) + { + items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(User.Id))); + } + return items.ToArray(); } } 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 @@ - - + +