1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-18 11:02:57 +08:00

Add client methods allowing users to be notified of who is watching them

This commit is contained in:
Bartłomiej Dach 2025-01-14 14:44:13 +01:00
parent 582c5180b9
commit 43fc48a3f3
No known key found for this signature in database
6 changed files with 97 additions and 14 deletions

View File

@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Online.Spectator;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
@ -15,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public partial class TestSceneSpectatorList : OsuTestScene
{
private readonly BindableList<SpectatorList.Spectator> spectators = new BindableList<SpectatorList.Spectator>();
private readonly BindableList<SpectatorUser> spectators = new BindableList<SpectatorUser>();
private readonly Bindable<LocalUserPlayingState> localUserPlayingState = new Bindable<LocalUserPlayingState>();
private int counter;
@ -36,7 +37,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("add a user", () =>
{
int id = Interlocked.Increment(ref counter);
spectators.Add(new SpectatorList.Spectator(id, $"User {id}"));
spectators.Add(new SpectatorUser
{
OnlineID = id,
Username = $"User {id}"
});
});
AddStep("remove random user", () => spectators.RemoveAt(RNG.Next(0, spectators.Count)));
AddStep("enter break", () => localUserPlayingState.Value = LocalUserPlayingState.Break);

View File

@ -37,5 +37,17 @@ namespace osu.Game.Online.Spectator
/// <param name="userId">The ID of the user who achieved the score.</param>
/// <param name="scoreId">The ID of the score.</param>
Task UserScoreProcessed(int userId, long scoreId);
/// <summary>
/// Signals that another user has <see cref="ISpectatorServer.StartWatchingUser">started watching this client</see>.
/// </summary>
/// <param name="user">The information about the user who started watching.</param>
Task UserStartedWatching(SpectatorUser[] user);
/// <summary>
/// Signals that another user has <see cref="ISpectatorServer.EndWatchingUser">ended watching this client</see>
/// </summary>
/// <param name="userId">The ID of the user who ended watching.</param>
Task UserEndedWatching(int userId);
}
}

View File

@ -42,6 +42,8 @@ namespace osu.Game.Online.Spectator
connection.On<int, FrameDataBundle>(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames);
connection.On<int, SpectatorState>(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying);
connection.On<int, long>(nameof(ISpectatorClient.UserScoreProcessed), ((ISpectatorClient)this).UserScoreProcessed);
connection.On<SpectatorUser[]>(nameof(ISpectatorClient.UserStartedWatching), ((ISpectatorClient)this).UserStartedWatching);
connection.On<int>(nameof(ISpectatorClient.UserEndedWatching), ((ISpectatorClient)this).UserEndedWatching);
connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IStatefulUserHubClient)this).DisconnectRequested);
};

View File

@ -36,9 +36,14 @@ namespace osu.Game.Online.Spectator
public abstract IBindable<bool> IsConnected { get; }
/// <summary>
/// The states of all users currently being watched.
/// The states of all users currently being watched by the local user.
/// </summary>
public virtual IBindableDictionary<int, SpectatorState> WatchedUserStates => watchedUserStates;
public IBindableDictionary<int, SpectatorState> WatchedUserStates => watchedUserStates;
/// <summary>
/// All users who are currently watching the local user.
/// </summary>
public IBindableList<SpectatorUser> WatchingUsers => watchingUsers;
/// <summary>
/// A global list of all players currently playing.
@ -82,6 +87,7 @@ namespace osu.Game.Online.Spectator
private readonly BindableDictionary<int, SpectatorState> watchedUserStates = new BindableDictionary<int, SpectatorState>();
private readonly BindableList<SpectatorUser> watchingUsers = new BindableList<SpectatorUser>();
private readonly BindableList<int> playingUsers = new BindableList<int>();
private readonly SpectatorState currentState = new SpectatorState();
@ -127,6 +133,7 @@ namespace osu.Game.Online.Spectator
{
playingUsers.Clear();
watchedUserStates.Clear();
watchingUsers.Clear();
}
}), true);
}
@ -179,6 +186,30 @@ namespace osu.Game.Online.Spectator
return Task.CompletedTask;
}
Task ISpectatorClient.UserStartedWatching(SpectatorUser[] users)
{
Schedule(() =>
{
foreach (var user in users)
{
if (!watchingUsers.Contains(user))
watchingUsers.Add(user);
}
});
return Task.CompletedTask;
}
Task ISpectatorClient.UserEndedWatching(int userId)
{
Schedule(() =>
{
watchingUsers.RemoveAll(u => u.OnlineID == userId);
});
return Task.CompletedTask;
}
Task IStatefulUserHubClient.DisconnectRequested()
{
Schedule(() => DisconnectInternal());

View File

@ -0,0 +1,39 @@
// 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 MessagePack;
using osu.Game.Users;
namespace osu.Game.Online.Spectator
{
[Serializable]
[MessagePackObject]
public class SpectatorUser : IUser, IEquatable<SpectatorUser>
{
[Key(0)]
public int OnlineID { get; set; }
[Key(1)]
public string Username { get; set; } = string.Empty;
[IgnoreMember]
public CountryCode CountryCode => CountryCode.Unknown;
[IgnoreMember]
public bool IsBot => false;
public bool Equals(SpectatorUser? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return OnlineID == other.OnlineID;
}
public override bool Equals(object? obj) => Equals(obj as SpectatorUser);
// ReSharper disable once NonReadonlyMemberInGetHashCode
public override int GetHashCode() => OnlineID;
}
}

View File

@ -13,9 +13,9 @@ using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osu.Game.Users;
using osu.Game.Localisation.HUD;
using osu.Game.Localisation.SkinComponents;
using osu.Game.Online.Spectator;
namespace osu.Game.Screens.Play.HUD
{
@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play.HUD
{
private const int max_spectators_displayed = 10;
public BindableList<Spectator> Spectators { get; } = new BindableList<Spectator>();
public BindableList<SpectatorUser> Spectators { get; } = new BindableList<SpectatorUser>();
public Bindable<LocalUserPlayingState> UserPlayingState { get; } = new Bindable<LocalUserPlayingState>();
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Font), nameof(SkinnableComponentStrings.FontDescription))]
@ -91,7 +91,7 @@ namespace osu.Game.Screens.Play.HUD
{
for (int i = 0; i < e.NewItems!.Count; i++)
{
var spectator = (Spectator)e.NewItems![i]!;
var spectator = (SpectatorUser)e.NewItems![i]!;
int index = e.NewStartingIndex + i;
if (index >= max_spectators_displayed)
@ -157,7 +157,7 @@ namespace osu.Game.Screens.Play.HUD
private partial class SpectatorListEntry : PoolableDrawable
{
public Bindable<Spectator> Current { get; } = new Bindable<Spectator>();
public Bindable<SpectatorUser> Current { get; } = new Bindable<SpectatorUser>();
private readonly BindableWithCurrent<LocalUserPlayingState> current = new BindableWithCurrent<LocalUserPlayingState>();
@ -209,11 +209,5 @@ namespace osu.Game.Screens.Play.HUD
linkCompiler.Enabled.Value = UserPlayingState.Value != LocalUserPlayingState.Playing;
}
}
public record Spectator(int OnlineID, string Username) : IUser
{
public CountryCode CountryCode => CountryCode.Unknown;
public bool IsBot => false;
}
}
}