mirror of
https://github.com/ppy/osu
synced 2024-12-14 10:57:41 +00:00
Merge pull request #25693 from bdach/online-presence-structure-serialisation
Simplify user presence structures for serialisability
This commit is contained in:
commit
3c29e9162c
@ -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();
|
||||||
|
@ -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>() });
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Colour = color ?? "000000",
|
Colour = color ?? "000000",
|
||||||
Status =
|
Status =
|
||||||
{
|
{
|
||||||
Value = new UserStatusOnline()
|
Value = UserStatus.Online
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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>();
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user