mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 12:33:01 +08:00
Merge pull request #25005 from minetoblend/multiplayer-invites
Add ability to invite players to multiplayer rooms
This commit is contained in:
commit
d5557bbbfb
@ -7,7 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.8" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" />
|
||||||
<PackageReference Include="nunit" Version="3.13.3" />
|
<PackageReference Include="nunit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -5,11 +5,13 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -31,6 +33,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
public double TimeToCompleteProgress { get; set; } = 2000;
|
public double TimeToCompleteProgress { get; set; } = 2000;
|
||||||
|
|
||||||
|
private readonly UserLookupCache userLookupCache = new TestUserLookupCache();
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
@ -60,6 +64,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep(@"simple #2", sendAmazingNotification);
|
AddStep(@"simple #2", sendAmazingNotification);
|
||||||
AddStep(@"progress #1", sendUploadProgress);
|
AddStep(@"progress #1", sendUploadProgress);
|
||||||
AddStep(@"progress #2", sendDownloadProgress);
|
AddStep(@"progress #2", sendDownloadProgress);
|
||||||
|
AddStep(@"User notification", sendUserNotification);
|
||||||
|
|
||||||
checkProgressingCount(2);
|
checkProgressingCount(2);
|
||||||
|
|
||||||
@ -537,6 +542,16 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
progressingNotifications.Add(n);
|
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()
|
private void sendUploadProgress()
|
||||||
{
|
{
|
||||||
var n = new ProgressNotification
|
var n = new ProgressNotification
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ViewBeatmap => new TranslatableString(getKey(@"view_beatmap"), @"View beatmap");
|
public static LocalisableString ViewBeatmap => new TranslatableString(getKey(@"view_beatmap"), @"View beatmap");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Invite player"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString InvitePlayer => new TranslatableString(getKey(@"invite_player"), @"Invite player");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,11 @@ Please try changing your audio device to a working setting.");
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
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);
|
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);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "{0} invited you to the multiplayer match "{1}"! Click to join."
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "You do not have the beatmap for this replay."
|
/// "You do not have the beatmap for this replay."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -14,6 +14,16 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"Playlist durations longer than 2 weeks require an active osu!supporter tag.");
|
public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"Playlist durations longer than 2 weeks require an active osu!supporter tag.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Can't invite this user as you have blocked them or they have blocked you."
|
||||||
|
/// </summary>
|
||||||
|
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.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Can't invite this user as they have opted out of non-friend communications."
|
||||||
|
/// </summary>
|
||||||
|
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}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,14 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// <param name="user">The user.</param>
|
/// <param name="user">The user.</param>
|
||||||
Task UserKicked(MultiplayerRoomUser user);
|
Task UserKicked(MultiplayerRoomUser user);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signals that the local user has been invited into a multiplayer room.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="invitedBy">Id of user that invited the player.</param>
|
||||||
|
/// <param name="roomID">Id of the room the user got invited to.</param>
|
||||||
|
/// <param name="password">Password to join the room.</param>
|
||||||
|
Task Invited(int invitedBy, long roomID, string password);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signal that the host of the room has changed.
|
/// Signal that the host of the room has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -99,5 +99,13 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="playlistItemId">The item to remove.</param>
|
/// <param name="playlistItemId">The item to remove.</param>
|
||||||
Task RemovePlaylistItem(long playlistItemId);
|
Task RemovePlaylistItem(long playlistItemId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invites a player to the current room.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user to invite.</param>
|
||||||
|
/// <exception cref="UserBlockedException">The user has blocked or has been blocked by the invited user.</exception>
|
||||||
|
/// <exception cref="UserBlocksPMsException">The invited user does not accept private messages.</exception>
|
||||||
|
Task InvitePlayer(int userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ using osu.Game.Overlays.Notifications;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Online.Multiplayer
|
namespace osu.Game.Online.Multiplayer
|
||||||
{
|
{
|
||||||
@ -30,6 +31,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
{
|
{
|
||||||
public Action<Notification>? PostNotification { protected get; set; }
|
public Action<Notification>? PostNotification { protected get; set; }
|
||||||
|
|
||||||
|
public Action<Room, string>? PresentMatch { protected get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when any change occurs to the multiplayer room.
|
/// Invoked when any change occurs to the multiplayer room.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -260,6 +263,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
protected abstract Task LeaveRoomInternal();
|
protected abstract Task LeaveRoomInternal();
|
||||||
|
|
||||||
|
public abstract Task InvitePlayer(int userId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Change the current <see cref="MultiplayerRoom"/> settings.
|
/// Change the current <see cref="MultiplayerRoom"/> settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -440,6 +445,38 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
return handleUserLeft(user, UserKicked);
|
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<Room?> getRoomAsync(long id)
|
||||||
|
{
|
||||||
|
TaskCompletionSource<Room?> taskCompletionSource = new TaskCompletionSource<Room?>();
|
||||||
|
|
||||||
|
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)
|
private void addUserToAPIRoom(MultiplayerRoomUser user)
|
||||||
{
|
{
|
||||||
Debug.Assert(APIRoom != null);
|
Debug.Assert(APIRoom != null);
|
||||||
|
@ -12,6 +12,8 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Online.Multiplayer
|
namespace osu.Game.Online.Multiplayer
|
||||||
{
|
{
|
||||||
@ -50,6 +52,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined);
|
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined);
|
||||||
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft);
|
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft);
|
||||||
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserKicked), ((IMultiplayerClient)this).UserKicked);
|
connection.On<MultiplayerRoomUser>(nameof(IMultiplayerClient.UserKicked), ((IMultiplayerClient)this).UserKicked);
|
||||||
|
connection.On<int, long, string>(nameof(IMultiplayerClient.Invited), ((IMultiplayerClient)this).Invited);
|
||||||
connection.On<int>(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged);
|
connection.On<int>(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged);
|
||||||
connection.On<MultiplayerRoomSettings>(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged);
|
connection.On<MultiplayerRoomSettings>(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged);
|
||||||
connection.On<int, MultiplayerUserState>(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged);
|
connection.On<int, MultiplayerUserState>(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged);
|
||||||
@ -106,6 +109,32 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom));
|
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)
|
public override Task TransferHost(int userId)
|
||||||
{
|
{
|
||||||
if (!IsConnected.Value)
|
if (!IsConnected.Value)
|
||||||
|
25
osu.Game/Online/Multiplayer/UserBlockedException.cs
Normal file
25
osu.Game/Online/Multiplayer/UserBlockedException.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
osu.Game/Online/Multiplayer/UserBlocksPMsException.cs
Normal file
25
osu.Game/Online/Multiplayer/UserBlocksPMsException.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,7 @@ using osu.Game.IO;
|
|||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapListing;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
using osu.Game.Overlays.Music;
|
using osu.Game.Overlays.Music;
|
||||||
@ -58,6 +59,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
@ -643,6 +645,24 @@ namespace osu.Game
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Join a multiplayer match immediately.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="room">The room to join.</param>
|
||||||
|
/// <param name="password">The password to join the room, if any is given.</param>
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Present a score's replay immediately.
|
/// Present a score's replay immediately.
|
||||||
/// The user should have already requested this interactively.
|
/// The user should have already requested this interactively.
|
||||||
@ -853,6 +873,7 @@ namespace osu.Game
|
|||||||
ScoreManager.PresentImport = items => PresentScore(items.First().Value);
|
ScoreManager.PresentImport = items => PresentScore(items.First().Value);
|
||||||
|
|
||||||
MultiplayerClient.PostNotification = n => Notifications.Post(n);
|
MultiplayerClient.PostNotification = n => Notifications.Post(n);
|
||||||
|
MultiplayerClient.PresentMatch = PresentMultiplayerMatch;
|
||||||
|
|
||||||
// make config aware of how to lookup skins for on-screen display purposes.
|
// 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.
|
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.
|
||||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Overlays.Dashboard
|
|||||||
{
|
{
|
||||||
users.GetUserAsync(userId).ContinueWith(task =>
|
users.GetUserAsync(userId).ContinueWith(task =>
|
||||||
{
|
{
|
||||||
var user = task.GetResultSafely();
|
APIUser user = task.GetResultSafely();
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
return;
|
return;
|
||||||
@ -130,6 +130,9 @@ namespace osu.Game.Overlays.Dashboard
|
|||||||
if (!playingUsers.Contains(user.Id))
|
if (!playingUsers.Contains(user.Id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// TODO: remove this once online state is being updated more correctly.
|
||||||
|
user.IsOnline = true;
|
||||||
|
|
||||||
userFlow.Add(createUserPanel(user));
|
userFlow.Add(createUserPanel(user));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -113,7 +113,8 @@ namespace osu.Game.Overlays
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Children = new[]
|
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) }),
|
new NotificationSection(NotificationsStrings.RunningTasks, new[] { typeof(ProgressNotification) }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,7 +206,8 @@ namespace osu.Game.Overlays
|
|||||||
var ourType = notification.GetType();
|
var ourType = notification.GetType();
|
||||||
int depth = notification.DisplayOnTop ? -runningDepth : runningDepth;
|
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);
|
section.Add(notification, depth);
|
||||||
|
|
||||||
|
@ -53,6 +53,8 @@ namespace osu.Game.Overlays.Notifications
|
|||||||
public virtual string PopInSampleName => "UI/notification-default";
|
public virtual string PopInSampleName => "UI/notification-default";
|
||||||
public virtual string PopOutSampleName => "UI/overlay-pop-out";
|
public virtual string PopOutSampleName => "UI/overlay-pop-out";
|
||||||
|
|
||||||
|
protected const float CORNER_RADIUS = 6;
|
||||||
|
|
||||||
protected NotificationLight Light;
|
protected NotificationLight Light;
|
||||||
|
|
||||||
protected Container IconContent;
|
protected Container IconContent;
|
||||||
@ -128,7 +130,7 @@ namespace osu.Game.Overlays.Notifications
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
}.WithChild(MainContent = new Container
|
}.WithChild(MainContent = new Container
|
||||||
{
|
{
|
||||||
CornerRadius = 6,
|
CornerRadius = CORNER_RADIUS,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
@ -473,10 +475,9 @@ namespace osu.Game.Overlays.Notifications
|
|||||||
base.Colour = value;
|
base.Colour = value;
|
||||||
pulsateLayer.EdgeEffect = new EdgeEffectParameters
|
pulsateLayer.EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Colour = ((Color4)value).Opacity(0.5f), //todo: avoid cast
|
Colour = ((Color4)value).Opacity(0.18f),
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Radius = 12,
|
Radius = 14,
|
||||||
Roundness = 12,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,13 +37,17 @@ namespace osu.Game.Overlays.Notifications
|
|||||||
notifications.Insert((int)position, notification);
|
notifications.Insert((int)position, notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Type> AcceptedNotificationTypes { get; }
|
/// <summary>
|
||||||
|
/// Enumerable of notification types accepted in this section.
|
||||||
|
/// If <see langword="null"/>, the section accepts any and all notifications.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<Type>? AcceptedNotificationTypes { get; }
|
||||||
|
|
||||||
private readonly LocalisableString titleText;
|
private readonly LocalisableString titleText;
|
||||||
|
|
||||||
public NotificationSection(LocalisableString title, IEnumerable<Type> acceptedNotificationTypes)
|
public NotificationSection(LocalisableString title, IEnumerable<Type>? acceptedNotificationTypes = null)
|
||||||
{
|
{
|
||||||
AcceptedNotificationTypes = acceptedNotificationTypes.ToArray();
|
AcceptedNotificationTypes = acceptedNotificationTypes?.ToArray();
|
||||||
|
|
||||||
titleText = title;
|
titleText = title;
|
||||||
}
|
}
|
||||||
|
74
osu.Game/Overlays/Notifications/UserAvatarNotification.cs
Normal file
74
osu.Game/Overlays/Notifications/UserAvatarNotification.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ using osu.Framework.Extensions.ObjectExtensions;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||||
|
|
||||||
@ -90,6 +91,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen();
|
protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen();
|
||||||
|
|
||||||
|
public void Join(Room room, string? password) => Schedule(() => Lounge.Join(room, password));
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
@ -26,14 +26,17 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
[Cached]
|
[Cached]
|
||||||
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
|
public IScreen CurrentSubScreen => screenStack.CurrentScreen;
|
||||||
|
|
||||||
public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true;
|
public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true;
|
||||||
|
|
||||||
// this is required due to PlayerLoader eventually being pushed to the main stack
|
// this is required due to PlayerLoader eventually being pushed to the main stack
|
||||||
// while leases may be taken out by a subscreen.
|
// while leases may be taken out by a subscreen.
|
||||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||||
|
|
||||||
|
protected LoungeSubScreen Lounge { get; private set; }
|
||||||
|
|
||||||
private MultiplayerWaveContainer waves;
|
private MultiplayerWaveContainer waves;
|
||||||
private LoungeSubScreen loungeSubScreen;
|
|
||||||
private ScreenStack screenStack;
|
private ScreenStack screenStack;
|
||||||
|
|
||||||
[Cached(Type = typeof(IRoomManager))]
|
[Cached(Type = typeof(IRoomManager))]
|
||||||
@ -89,7 +92,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
screenStack.ScreenPushed += screenPushed;
|
screenStack.ScreenPushed += screenPushed;
|
||||||
screenStack.ScreenExited += screenExited;
|
screenStack.ScreenExited += screenExited;
|
||||||
|
|
||||||
screenStack.Push(loungeSubScreen = CreateLounge());
|
screenStack.Push(Lounge = CreateLounge());
|
||||||
|
|
||||||
apiState.BindTo(API.State);
|
apiState.BindTo(API.State);
|
||||||
apiState.BindValueChanged(onlineStateChanged, true);
|
apiState.BindValueChanged(onlineStateChanged, true);
|
||||||
@ -120,10 +123,10 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
Mods.SetDefault();
|
Mods.SetDefault();
|
||||||
|
|
||||||
if (loungeSubScreen.IsCurrentScreen())
|
if (Lounge.IsCurrentScreen())
|
||||||
loungeSubScreen.OnEntering(e);
|
Lounge.OnEntering(e);
|
||||||
else
|
else
|
||||||
loungeSubScreen.MakeCurrent();
|
Lounge.MakeCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnResuming(ScreenTransitionEvent e)
|
public override void OnResuming(ScreenTransitionEvent e)
|
||||||
@ -224,8 +227,6 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
|
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IScreen CurrentSubScreen => screenStack.CurrentScreen;
|
|
||||||
|
|
||||||
protected abstract string ScreenTitle { get; }
|
protected abstract string ScreenTitle { get; }
|
||||||
|
|
||||||
protected virtual RoomManager CreateRoomManager() => new RoomManager();
|
protected virtual RoomManager CreateRoomManager() => new RoomManager();
|
||||||
|
@ -263,6 +263,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task InvitePlayer(int userId)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
public override Task TransferHost(int userId)
|
public override Task TransferHost(int userId)
|
||||||
{
|
{
|
||||||
userId = clone(userId);
|
userId = clone(userId);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -18,6 +19,7 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
|
||||||
namespace osu.Game.Users
|
namespace osu.Game.Users
|
||||||
{
|
{
|
||||||
@ -61,6 +63,9 @@ namespace osu.Game.Users
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
protected OsuColour Colours { get; private set; } = null!;
|
protected OsuColour Colours { get; private set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private MultiplayerClient? multiplayerClient { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
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();
|
return items.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,13 @@
|
|||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
||||||
<PackageReference Include="DiffPlex" Version="1.7.1" />
|
<PackageReference Include="DiffPlex" Version="1.7.1" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.53" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.54" />
|
||||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||||
<PackageReference Include="MessagePack" Version="2.5.124" />
|
<PackageReference Include="MessagePack" Version="2.5.129" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.12" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="7.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="7.0.12" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="7.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="7.0.12" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.11" />
|
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.12" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
@ -38,8 +38,8 @@
|
|||||||
<PackageReference Include="Realm" Version="11.5.0" />
|
<PackageReference Include="Realm" Version="11.5.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2023.1012.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2023.1012.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.1003.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.1003.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.39.1" />
|
<PackageReference Include="Sentry" Version="3.40.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.33.0" />
|
<PackageReference Include="SharpCompress" Version="0.34.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.6" />
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.6" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user