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 @@
-
-
+
+