Merge pull request #25693 from bdach/online-presence-structure-serialisation

Simplify user presence structures for serialisability
This commit is contained in:
Dean Herbert 2023-12-07 17:39:21 +09:00 committed by GitHub
commit 3c29e9162c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 283 additions and 171 deletions

View File

@ -9,7 +9,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -34,7 +33,7 @@ namespace osu.Desktop
[Resolved] [Resolved]
private IAPIProvider api { get; set; } = null!; private IAPIProvider api { get; set; } = null!;
private readonly IBindable<UserStatus> status = new Bindable<UserStatus>(); private readonly IBindable<UserStatus?> status = new Bindable<UserStatus?>();
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>(); private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>(); private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();
@ -87,25 +86,26 @@ namespace osu.Desktop
if (!client.IsInitialized) if (!client.IsInitialized)
return; return;
if (status.Value is UserStatusOffline || privacyMode.Value == DiscordRichPresenceMode.Off) if (status.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off)
{ {
client.ClearPresence(); client.ClearPresence();
return; return;
} }
if (status.Value is UserStatusOnline && activity.Value != null) if (status.Value == UserStatus.Online && activity.Value != null)
{ {
presence.State = truncate(activity.Value.GetStatus(privacyMode.Value == DiscordRichPresenceMode.Limited)); bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited;
presence.Details = truncate(getDetails(activity.Value)); presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation));
presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty);
if (getBeatmap(activity.Value) is IBeatmapInfo beatmap && beatmap.OnlineID > 0) if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0)
{ {
presence.Buttons = new[] presence.Buttons = new[]
{ {
new Button new Button
{ {
Label = "View beatmap", Label = "View beatmap",
Url = $@"{api.WebsiteRootUrl}/beatmapsets/{beatmap.BeatmapSet?.OnlineID}#{ruleset.Value.ShortName}/{beatmap.OnlineID}" Url = $@"{api.WebsiteRootUrl}/beatmaps/{beatmapId}?mode={ruleset.Value.ShortName}"
} }
}; };
} }
@ -159,40 +159,20 @@ namespace osu.Desktop
}); });
} }
private IBeatmapInfo? getBeatmap(UserActivity activity) private int? getBeatmapID(UserActivity activity)
{ {
switch (activity) switch (activity)
{ {
case UserActivity.InGame game: case UserActivity.InGame game:
return game.BeatmapInfo; return game.BeatmapID;
case UserActivity.EditingBeatmap edit: case UserActivity.EditingBeatmap edit:
return edit.BeatmapInfo; return edit.BeatmapID;
} }
return null; return null;
} }
private string getDetails(UserActivity activity)
{
switch (activity)
{
case UserActivity.InGame game:
return game.BeatmapInfo.ToString() ?? string.Empty;
case UserActivity.EditingBeatmap edit:
return edit.BeatmapInfo.ToString() ?? string.Empty;
case UserActivity.WatchingReplay watching:
return watching.BeatmapInfo?.ToString() ?? string.Empty;
case UserActivity.InLobby lobby:
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
}
return string.Empty;
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
client.Dispose(); client.Dispose();

View File

@ -11,8 +11,8 @@ using osu.Game.Beatmaps;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online namespace osu.Game.Tests.Visual.Online
@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestPlayActivity() public void TestPlayActivity()
{ {
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new RulesetInfo())); AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo));
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel()))); AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestModPresence() public void TestModPresence()
{ {
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new RulesetInfo())); AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new OsuRuleset().RulesetInfo));
AddStep("Add Hidden mod", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateMod<ModHidden>() }); AddStep("Add Hidden mod", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateMod<ModHidden>() });

View File

@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online
Colour = color ?? "000000", Colour = color ?? "000000",
Status = Status =
{ {
Value = new UserStatusOnline() Value = UserStatus.Online
}, },
}; };

View File

@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online
public partial class TestSceneUserPanel : OsuTestScene public partial class TestSceneUserPanel : OsuTestScene
{ {
private readonly Bindable<UserActivity> activity = new Bindable<UserActivity>(); private readonly Bindable<UserActivity> activity = new Bindable<UserActivity>();
private readonly Bindable<UserStatus> status = new Bindable<UserStatus>(); private readonly Bindable<UserStatus?> status = new Bindable<UserStatus?>();
private UserGridPanel boundPanel1; private UserGridPanel boundPanel1;
private TestUserListPanel boundPanel2; private TestUserListPanel boundPanel2;
@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Online
Id = 3103765, Id = 3103765,
CountryCode = CountryCode.JP, CountryCode = CountryCode.JP,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg",
Status = { Value = new UserStatusOnline() } Status = { Value = UserStatus.Online }
}) { Width = 300 }, }) { Width = 300 },
boundPanel1 = new UserGridPanel(new APIUser boundPanel1 = new UserGridPanel(new APIUser
{ {
@ -99,16 +99,16 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestUserStatus() public void TestUserStatus()
{ {
AddStep("online", () => status.Value = new UserStatusOnline()); AddStep("online", () => status.Value = UserStatus.Online);
AddStep("do not disturb", () => status.Value = new UserStatusDoNotDisturb()); AddStep("do not disturb", () => status.Value = UserStatus.DoNotDisturb);
AddStep("offline", () => status.Value = new UserStatusOffline()); AddStep("offline", () => status.Value = UserStatus.Offline);
AddStep("null status", () => status.Value = null); AddStep("null status", () => status.Value = null);
} }
[Test] [Test]
public void TestUserActivity() public void TestUserActivity()
{ {
AddStep("set online status", () => status.Value = new UserStatusOnline()); AddStep("set online status", () => status.Value = UserStatus.Online);
AddStep("idle", () => activity.Value = null); AddStep("idle", () => activity.Value = null);
AddStep("watching replay", () => activity.Value = new UserActivity.WatchingReplay(createScore(@"nats"))); AddStep("watching replay", () => activity.Value = new UserActivity.WatchingReplay(createScore(@"nats")));
@ -127,12 +127,12 @@ namespace osu.Game.Tests.Visual.Online
public void TestUserActivityChange() public void TestUserActivityChange()
{ {
AddAssert("visit message is visible", () => boundPanel2.LastVisitMessage.IsPresent); AddAssert("visit message is visible", () => boundPanel2.LastVisitMessage.IsPresent);
AddStep("set online status", () => status.Value = new UserStatusOnline()); AddStep("set online status", () => status.Value = UserStatus.Online);
AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent); AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent);
AddStep("set choosing activity", () => activity.Value = new UserActivity.ChoosingBeatmap()); AddStep("set choosing activity", () => activity.Value = new UserActivity.ChoosingBeatmap());
AddStep("set offline status", () => status.Value = new UserStatusOffline()); AddStep("set offline status", () => status.Value = UserStatus.Offline);
AddAssert("visit message is visible", () => boundPanel2.LastVisitMessage.IsPresent); AddAssert("visit message is visible", () => boundPanel2.LastVisitMessage.IsPresent);
AddStep("set online status", () => status.Value = new UserStatusOnline()); AddStep("set online status", () => status.Value = UserStatus.Online);
AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent); AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent);
} }

View File

@ -247,7 +247,7 @@ namespace osu.Game.Online.API
userReq.Success += user => userReq.Success += user =>
{ {
// todo: save/pull from settings // todo: save/pull from settings
user.Status.Value = new UserStatusOnline(); user.Status.Value = UserStatus.Online;
setLocalUser(user); setLocalUser(user);

View File

@ -43,7 +43,7 @@ namespace osu.Game.Online.API.Requests.Responses
set => countryCodeString = value.ToString(); set => countryCodeString = value.ToString();
} }
public readonly Bindable<UserStatus> Status = new Bindable<UserStatus>(); public readonly Bindable<UserStatus?> Status = new Bindable<UserStatus?>();
public readonly Bindable<UserActivity> Activity = new Bindable<UserActivity>(); public readonly Bindable<UserActivity> Activity = new Bindable<UserActivity>();

View File

@ -7,7 +7,6 @@ using System.Text;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -33,9 +32,6 @@ namespace osu.Game.Online.Chat
[Resolved] [Resolved]
private IBindable<RulesetInfo> currentRuleset { get; set; } = null!; private IBindable<RulesetInfo> currentRuleset { get; set; } = null!;
[Resolved]
private LocalisationManager localisation { get; set; } = null!;
private readonly Channel? target; private readonly Channel? target;
/// <summary> /// <summary>
@ -52,23 +48,28 @@ namespace osu.Game.Online.Chat
base.LoadComplete(); base.LoadComplete();
string verb; string verb;
IBeatmapInfo beatmapInfo;
int beatmapOnlineID;
string beatmapDisplayTitle;
switch (api.Activity.Value) switch (api.Activity.Value)
{ {
case UserActivity.InGame game: case UserActivity.InGame game:
verb = "playing"; verb = "playing";
beatmapInfo = game.BeatmapInfo; beatmapOnlineID = game.BeatmapID;
beatmapDisplayTitle = game.BeatmapDisplayTitle;
break; break;
case UserActivity.EditingBeatmap edit: case UserActivity.EditingBeatmap edit:
verb = "editing"; verb = "editing";
beatmapInfo = edit.BeatmapInfo; beatmapOnlineID = edit.BeatmapID;
beatmapDisplayTitle = edit.BeatmapDisplayTitle;
break; break;
default: default:
verb = "listening to"; verb = "listening to";
beatmapInfo = currentBeatmap.Value.BeatmapInfo; beatmapOnlineID = currentBeatmap.Value.BeatmapInfo.OnlineID;
beatmapDisplayTitle = currentBeatmap.Value.BeatmapInfo.GetDisplayTitle();
break; break;
} }
@ -86,9 +87,7 @@ namespace osu.Game.Online.Chat
string getBeatmapPart() string getBeatmapPart()
{ {
string beatmapInfoString = localisation.GetLocalisedBindableString(beatmapInfo.GetDisplayTitleRomanisable()).Value; return beatmapOnlineID > 0 ? $"[{api.WebsiteRootUrl}/b/{beatmapOnlineID} {beatmapDisplayTitle}]" : beatmapDisplayTitle;
return beatmapInfo.OnlineID > 0 ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineID} {beatmapInfoString}]" : beatmapInfoString;
} }
string getRulesetPart() string getRulesetPart()

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Multiplayer.Countdown;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Users;
namespace osu.Game.Online namespace osu.Game.Online
{ {
@ -18,6 +19,7 @@ namespace osu.Game.Online
{ {
internal static readonly IReadOnlyList<(Type derivedType, Type baseType)> BASE_TYPE_MAPPING = new[] internal static readonly IReadOnlyList<(Type derivedType, Type baseType)> BASE_TYPE_MAPPING = new[]
{ {
// multiplayer
(typeof(ChangeTeamRequest), typeof(MatchUserRequest)), (typeof(ChangeTeamRequest), typeof(MatchUserRequest)),
(typeof(StartMatchCountdownRequest), typeof(MatchUserRequest)), (typeof(StartMatchCountdownRequest), typeof(MatchUserRequest)),
(typeof(StopCountdownRequest), typeof(MatchUserRequest)), (typeof(StopCountdownRequest), typeof(MatchUserRequest)),
@ -28,6 +30,20 @@ namespace osu.Game.Online
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown)), (typeof(MatchStartCountdown), typeof(MultiplayerCountdown)),
(typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown)), (typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown)),
(typeof(ServerShuttingDownCountdown), typeof(MultiplayerCountdown)), (typeof(ServerShuttingDownCountdown), typeof(MultiplayerCountdown)),
// metadata
(typeof(UserActivity.ChoosingBeatmap), typeof(UserActivity)),
(typeof(UserActivity.InSoloGame), typeof(UserActivity)),
(typeof(UserActivity.WatchingReplay), typeof(UserActivity)),
(typeof(UserActivity.SpectatingUser), typeof(UserActivity)),
(typeof(UserActivity.SearchingForLobby), typeof(UserActivity)),
(typeof(UserActivity.InLobby), typeof(UserActivity)),
(typeof(UserActivity.InMultiplayerGame), typeof(UserActivity)),
(typeof(UserActivity.SpectatingMultiplayerGame), typeof(UserActivity)),
(typeof(UserActivity.InPlaylistGame), typeof(UserActivity)),
(typeof(UserActivity.EditingBeatmap), typeof(UserActivity)),
(typeof(UserActivity.ModdingBeatmap), typeof(UserActivity)),
(typeof(UserActivity.TestingBeatmap), typeof(UserActivity)),
}; };
} }
} }

View File

@ -148,17 +148,17 @@ namespace osu.Game.Overlays.Login
switch (action.NewValue) switch (action.NewValue)
{ {
case UserAction.Online: case UserAction.Online:
api.LocalUser.Value.Status.Value = new UserStatusOnline(); api.LocalUser.Value.Status.Value = UserStatus.Online;
dropdown.StatusColour = colours.Green; dropdown.StatusColour = colours.Green;
break; break;
case UserAction.DoNotDisturb: case UserAction.DoNotDisturb:
api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb(); api.LocalUser.Value.Status.Value = UserStatus.DoNotDisturb;
dropdown.StatusColour = colours.Red; dropdown.StatusColour = colours.Red;
break; break;
case UserAction.AppearOffline: case UserAction.AppearOffline:
api.LocalUser.Value.Status.Value = new UserStatusOffline(); api.LocalUser.Value.Status.Value = UserStatus.Offline;
dropdown.StatusColour = colours.Gray7; dropdown.StatusColour = colours.Gray7;
break; break;

View File

@ -6,6 +6,7 @@
using osuTK; using osuTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -18,7 +19,7 @@ namespace osu.Game.Users
{ {
public abstract partial class ExtendedUserPanel : UserPanel public abstract partial class ExtendedUserPanel : UserPanel
{ {
public readonly Bindable<UserStatus> Status = new Bindable<UserStatus>(); public readonly Bindable<UserStatus?> Status = new Bindable<UserStatus?>();
public readonly IBindable<UserActivity> Activity = new Bindable<UserActivity>(); public readonly IBindable<UserActivity> Activity = new Bindable<UserActivity>();
@ -97,14 +98,14 @@ namespace osu.Game.Users
return statusContainer; return statusContainer;
} }
private void displayStatus(UserStatus status, UserActivity activity = null) private void displayStatus(UserStatus? status, UserActivity activity = null)
{ {
if (status != null) if (status != null)
{ {
LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); LastVisitMessage.FadeTo(status == UserStatus.Offline && User.LastVisit.HasValue ? 1 : 0);
// Set status message based on activity (if we have one) and status is not offline // Set status message based on activity (if we have one) and status is not offline
if (activity != null && !(status is UserStatusOffline)) if (activity != null && status != UserStatus.Offline)
{ {
statusMessage.Text = activity.GetStatus(); statusMessage.Text = activity.GetStatus();
statusIcon.FadeColour(activity.GetAppropriateColour(Colours), 500, Easing.OutQuint); statusIcon.FadeColour(activity.GetAppropriateColour(Colours), 500, Easing.OutQuint);
@ -112,8 +113,8 @@ namespace osu.Game.Users
} }
// Otherwise use only status // Otherwise use only status
statusMessage.Text = status.Message; statusMessage.Text = status.GetLocalisableDescription();
statusIcon.FadeColour(status.GetAppropriateColour(Colours), 500, Easing.OutQuint); statusIcon.FadeColour(status.Value.GetAppropriateColour(Colours), 500, Easing.OutQuint);
return; return;
} }
@ -121,11 +122,11 @@ namespace osu.Game.Users
// Fallback to web status if local one is null // Fallback to web status if local one is null
if (User.IsOnline) if (User.IsOnline)
{ {
Status.Value = new UserStatusOnline(); Status.Value = UserStatus.Online;
return; return;
} }
Status.Value = new UserStatusOffline(); Status.Value = UserStatus.Offline;
} }
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)

View File

@ -1,8 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using MessagePack;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Online;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -10,43 +13,84 @@ using osuTK.Graphics;
namespace osu.Game.Users namespace osu.Game.Users
{ {
/// <summary>
/// Base class for all structures describing the user's current activity.
/// </summary>
/// <remarks>
/// Warning: keep <see cref="UnionAttribute"/> specs consistent with
/// <see cref="SignalRWorkaroundTypes.BASE_TYPE_MAPPING"/>.
/// </remarks>
[Serializable]
[MessagePackObject]
[Union(11, typeof(ChoosingBeatmap))]
[Union(12, typeof(InSoloGame))]
[Union(13, typeof(WatchingReplay))]
[Union(14, typeof(SpectatingUser))]
[Union(21, typeof(SearchingForLobby))]
[Union(22, typeof(InLobby))]
[Union(23, typeof(InMultiplayerGame))]
[Union(24, typeof(SpectatingMultiplayerGame))]
[Union(31, typeof(InPlaylistGame))]
[Union(41, typeof(EditingBeatmap))]
[Union(42, typeof(ModdingBeatmap))]
[Union(43, typeof(TestingBeatmap))]
public abstract class UserActivity public abstract class UserActivity
{ {
public abstract string GetStatus(bool hideIdentifiableInformation = false); public abstract string GetStatus(bool hideIdentifiableInformation = false);
public virtual string? GetDetails(bool hideIdentifiableInformation = false) => null;
public virtual Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDarker; public virtual Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDarker;
public class ModdingBeatmap : EditingBeatmap [MessagePackObject]
{
public override string GetStatus(bool hideIdentifiableInformation = false) => "Modding a beatmap";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
public ModdingBeatmap(IBeatmapInfo info)
: base(info)
{
}
}
public class ChoosingBeatmap : UserActivity public class ChoosingBeatmap : UserActivity
{ {
public override string GetStatus(bool hideIdentifiableInformation = false) => "Choosing a beatmap"; public override string GetStatus(bool hideIdentifiableInformation = false) => "Choosing a beatmap";
} }
[MessagePackObject]
public abstract class InGame : UserActivity public abstract class InGame : UserActivity
{ {
public IBeatmapInfo BeatmapInfo { get; } [Key(0)]
public int BeatmapID { get; set; }
public IRulesetInfo Ruleset { get; } [Key(1)]
public string BeatmapDisplayTitle { get; set; } = string.Empty;
[Key(2)]
public int RulesetID { get; set; }
[Key(3)]
public string RulesetPlayingVerb { get; set; } = string.Empty; // TODO: i'm going with this for now, but this is wasteful
protected InGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) protected InGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
{ {
BeatmapInfo = beatmapInfo; BeatmapID = beatmapInfo.OnlineID;
Ruleset = ruleset; BeatmapDisplayTitle = beatmapInfo.GetDisplayTitle();
RulesetID = ruleset.OnlineID;
RulesetPlayingVerb = ruleset.CreateInstance().PlayingVerb;
} }
public override string GetStatus(bool hideIdentifiableInformation = false) => Ruleset.CreateInstance().PlayingVerb; [SerializationConstructor]
protected InGame() { }
public override string GetStatus(bool hideIdentifiableInformation = false) => RulesetPlayingVerb;
public override string GetDetails(bool hideIdentifiableInformation = false) => BeatmapDisplayTitle;
} }
[MessagePackObject]
public class InSoloGame : InGame
{
public InSoloGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
[SerializationConstructor]
public InSoloGame() { }
}
[MessagePackObject]
public class InMultiplayerGame : InGame public class InMultiplayerGame : InGame
{ {
public InMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) public InMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
@ -54,9 +98,122 @@ namespace osu.Game.Users
{ {
} }
[SerializationConstructor]
public InMultiplayerGame()
{
}
public override string GetStatus(bool hideIdentifiableInformation = false) => $@"{base.GetStatus(hideIdentifiableInformation)} with others"; public override string GetStatus(bool hideIdentifiableInformation = false) => $@"{base.GetStatus(hideIdentifiableInformation)} with others";
} }
[MessagePackObject]
public class InPlaylistGame : InGame
{
public InPlaylistGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
[SerializationConstructor]
public InPlaylistGame() { }
}
[MessagePackObject]
public class TestingBeatmap : InGame
{
public TestingBeatmap(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
[SerializationConstructor]
public TestingBeatmap() { }
public override string GetStatus(bool hideIdentifiableInformation = false) => "Testing a beatmap";
}
[MessagePackObject]
public class EditingBeatmap : UserActivity
{
[Key(0)]
public int BeatmapID { get; set; }
[Key(1)]
public string BeatmapDisplayTitle { get; set; } = string.Empty;
public EditingBeatmap(IBeatmapInfo info)
{
BeatmapID = info.OnlineID;
BeatmapDisplayTitle = info.GetDisplayTitle();
}
[SerializationConstructor]
public EditingBeatmap() { }
public override string GetStatus(bool hideIdentifiableInformation = false) => @"Editing a beatmap";
public override string GetDetails(bool hideIdentifiableInformation = false) => BeatmapDisplayTitle;
}
[MessagePackObject]
public class ModdingBeatmap : EditingBeatmap
{
public ModdingBeatmap(IBeatmapInfo info)
: base(info)
{
}
[SerializationConstructor]
public ModdingBeatmap() { }
public override string GetStatus(bool hideIdentifiableInformation = false) => "Modding a beatmap";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
}
[MessagePackObject]
public class WatchingReplay : UserActivity
{
[Key(0)]
public long ScoreID { get; set; }
[Key(1)]
public string PlayerName { get; set; } = string.Empty;
[Key(2)]
public int BeatmapID { get; set; }
[Key(3)]
public string? BeatmapDisplayTitle { get; set; }
public WatchingReplay(ScoreInfo score)
{
ScoreID = score.OnlineID;
PlayerName = score.User.Username;
BeatmapID = score.BeatmapInfo?.OnlineID ?? -1;
BeatmapDisplayTitle = score.BeatmapInfo?.GetDisplayTitle();
}
[SerializationConstructor]
public WatchingReplay() { }
public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Watching a replay" : $@"Watching {PlayerName}'s replay";
public override string? GetDetails(bool hideIdentifiableInformation = false) => BeatmapDisplayTitle;
}
[MessagePackObject]
public class SpectatingUser : WatchingReplay
{
public SpectatingUser(ScoreInfo score)
: base(score)
{
}
[SerializationConstructor]
public SpectatingUser() { }
public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Spectating a user" : $@"Spectating {PlayerName}";
}
[MessagePackObject]
public class SpectatingMultiplayerGame : InGame public class SpectatingMultiplayerGame : InGame
{ {
public SpectatingMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset) public SpectatingMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
@ -64,88 +221,41 @@ namespace osu.Game.Users
{ {
} }
[SerializationConstructor]
public SpectatingMultiplayerGame() { }
public override string GetStatus(bool hideIdentifiableInformation = false) => $"Watching others {base.GetStatus(hideIdentifiableInformation).ToLowerInvariant()}"; public override string GetStatus(bool hideIdentifiableInformation = false) => $"Watching others {base.GetStatus(hideIdentifiableInformation).ToLowerInvariant()}";
} }
public class InPlaylistGame : InGame [MessagePackObject]
{
public InPlaylistGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
}
public class InSoloGame : InGame
{
public InSoloGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
}
public class TestingBeatmap : InGame
{
public override string GetStatus(bool hideIdentifiableInformation = false) => "Testing a beatmap";
public TestingBeatmap(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
: base(beatmapInfo, ruleset)
{
}
}
public class EditingBeatmap : UserActivity
{
public IBeatmapInfo BeatmapInfo { get; }
public EditingBeatmap(IBeatmapInfo info)
{
BeatmapInfo = info;
}
public override string GetStatus(bool hideIdentifiableInformation = false) => @"Editing a beatmap";
}
public class WatchingReplay : UserActivity
{
private readonly ScoreInfo score;
protected string Username => score.User.Username;
public BeatmapInfo? BeatmapInfo => score.BeatmapInfo;
public WatchingReplay(ScoreInfo score)
{
this.score = score;
}
public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Watching a replay" : $@"Watching {Username}'s replay";
}
public class SpectatingUser : WatchingReplay
{
public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Spectating a user" : $@"Spectating {Username}";
public SpectatingUser(ScoreInfo score)
: base(score)
{
}
}
public class SearchingForLobby : UserActivity public class SearchingForLobby : UserActivity
{ {
public override string GetStatus(bool hideIdentifiableInformation = false) => @"Looking for a lobby"; public override string GetStatus(bool hideIdentifiableInformation = false) => @"Looking for a lobby";
} }
[MessagePackObject]
public class InLobby : UserActivity public class InLobby : UserActivity
{ {
public override string GetStatus(bool hideIdentifiableInformation = false) => @"In a lobby"; [Key(0)]
public long RoomID { get; set; }
public readonly Room Room; [Key(1)]
public string RoomName { get; set; } = string.Empty;
public InLobby(Room room) public InLobby(Room room)
{ {
Room = room; RoomID = room.RoomID.Value ?? -1;
RoomName = room.Name.Value;
} }
[SerializationConstructor]
public InLobby() { }
public override string GetStatus(bool hideIdentifiableInformation = false) => @"In a lobby";
public override string? GetDetails(bool hideIdentifiableInformation = false) => hideIdentifiableInformation
? null
: RoomName;
} }
} }
} }

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using System.ComponentModel;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -8,32 +10,36 @@ using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Users namespace osu.Game.Users
{ {
public abstract class UserStatus public enum UserStatus
{ {
public abstract LocalisableString Message { get; } [LocalisableDescription(typeof(UsersStrings), nameof(UsersStrings.StatusOffline))]
public abstract Color4 GetAppropriateColour(OsuColour colours); Offline,
[Description("Do not disturb")]
DoNotDisturb,
[LocalisableDescription(typeof(UsersStrings), nameof(UsersStrings.StatusOnline))]
Online,
} }
public class UserStatusOnline : UserStatus public static class UserStatusExtensions
{ {
public override LocalisableString Message => UsersStrings.StatusOnline; public static Color4 GetAppropriateColour(this UserStatus userStatus, OsuColour colours)
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight; {
} switch (userStatus)
{
case UserStatus.Offline:
return Color4.Black;
public abstract class UserStatusBusy : UserStatusOnline case UserStatus.DoNotDisturb:
{ return colours.RedDark;
public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDark;
}
public class UserStatusOffline : UserStatus case UserStatus.Online:
{ return colours.GreenDark;
public override LocalisableString Message => UsersStrings.StatusOffline;
public override Color4 GetAppropriateColour(OsuColour colours) => Color4.Black;
}
public class UserStatusDoNotDisturb : UserStatus default:
{ throw new ArgumentOutOfRangeException(nameof(userStatus), userStatus, "Unsupported user status");
public override LocalisableString Message => "Do not disturb"; }
public override Color4 GetAppropriateColour(OsuColour colours) => colours.RedDark; }
} }
} }