diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index a4df450db9..df4b85b37a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; @@ -227,12 +229,19 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded); } - internal class TestSpectatorStreamingClient : SpectatorStreamingClient + public class TestSpectatorStreamingClient : SpectatorStreamingClient { public readonly User StreamingUser = new User { Id = 1234, Username = "Test user" }; + public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; + private int beatmapId; + protected override Task Connect() + { + return Task.CompletedTask; + } + public void StartPlay(int beatmapId) { this.beatmapId = beatmapId; diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs new file mode 100644 index 0000000000..d6fd33bce7 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Spectator; +using osu.Game.Overlays.Dashboard; +using osu.Game.Tests.Visual.Gameplay; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneCurrentlyPlayingDisplay : OsuTestScene + { + [Cached(typeof(SpectatorStreamingClient))] + private TestSceneSpectator.TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSceneSpectator.TestSpectatorStreamingClient(); + + private CurrentlyPlayingDisplay currentlyPlaying; + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case GetUserRequest cRequest: + cRequest.TriggerSuccess(new User { Username = "peppy", Id = 2 }); + break; + } + }); + + AddStep("add streaming client", () => + { + Remove(testSpectatorStreamingClient); + + Children = new Drawable[] + { + testSpectatorStreamingClient, + currentlyPlaying = new CurrentlyPlayingDisplay + { + RelativeSizeAxes = Axes.Both, + } + }; + }); + + AddStep("Reset players", () => testSpectatorStreamingClient.PlayingUsers.Clear()); + } + + [Test] + public void TestBasicDisplay() + { + AddStep("Add playing user", () => testSpectatorStreamingClient.PlayingUsers.Add(2)); + AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType()?.FirstOrDefault()?.User.Id == 2); + AddStep("Remove playing user", () => testSpectatorStreamingClient.PlayingUsers.Remove(2)); + AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType().Any()); + } + } +} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 26f72d3455..8206a92a54 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -42,6 +42,8 @@ namespace osu.Game.Configuration Set(OsuSetting.Username, string.Empty); Set(OsuSetting.Token, string.Empty); + Set(OsuSetting.AutomaticallyDownloadWhenSpectating, false); + Set(OsuSetting.SavePassword, false).ValueChanged += enabled => { if (enabled.NewValue) Set(OsuSetting.SaveUsername, true); @@ -245,5 +247,6 @@ namespace osu.Game.Configuration GameplayDisableWinKey, SeasonalBackgroundMode, EditorWaveformOpacity, + AutomaticallyDownloadWhenSpectating, } } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 2367651e04..9ba81720d8 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -93,14 +93,14 @@ namespace osu.Game.Online.Spectator break; case APIState.Online: - Task.Run(connect); + Task.Run(Connect); break; } } private const string endpoint = "https://spectator.ppy.sh/spectator"; - private async Task connect() + protected virtual async Task Connect() { if (connection != null) return; diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs new file mode 100644 index 0000000000..34444c2fa5 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Specialized; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Spectator; +using osu.Game.Screens.Multi.Match.Components; +using osu.Game.Screens.Play; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Dashboard +{ + internal class CurrentlyPlayingDisplay : CompositeDrawable + { + private IBindableList playingUsers; + + private FillFlowContainer userFlow; + + [Resolved] + private SpectatorStreamingClient spectatorStreaming { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = userFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + }; + } + + [Resolved] + private IAPIProvider api { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + playingUsers = spectatorStreaming.PlayingUsers.GetBoundCopy(); + playingUsers.BindCollectionChanged((sender, e) => Schedule(() => + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (var u in e.NewItems.OfType()) + { + var request = new GetUserRequest(u); + request.Success += user => Schedule(() => + { + if (playingUsers.Contains((int)user.Id)) + userFlow.Add(createUserPanel(user)); + }); + api.Queue(request); + } + + break; + + case NotifyCollectionChangedAction.Remove: + foreach (var u in e.OldItems.OfType()) + userFlow.FirstOrDefault(card => card.User.Id == u)?.Expire(); + break; + + case NotifyCollectionChangedAction.Reset: + userFlow.Clear(); + break; + } + }), true); + } + + private PlayingUserPanel createUserPanel(User user) => + new PlayingUserPanel(user).With(panel => + { + panel.Anchor = Anchor.TopCentre; + panel.Origin = Anchor.TopCentre; + }); + + private class PlayingUserPanel : CompositeDrawable + { + public readonly User User; + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + public PlayingUserPanel(User user) + { + User = user; + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + Width = 290, + Children = new Drawable[] + { + new UserGridPanel(user) + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new PurpleTriangleButton + { + RelativeSizeAxes = Axes.X, + Text = "Watch", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Action = () => game?.PerformFromScreen(s => s.Push(new Spectator(user))) + } + } + }, + }; + } + } + } +} diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 36bf589877..3314ed957a 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; + namespace osu.Game.Overlays.Dashboard { public class DashboardOverlayHeader : TabControlOverlayHeader @@ -20,6 +22,9 @@ namespace osu.Game.Overlays.Dashboard public enum DashboardOverlayTabs { - Friends + Friends, + + [Description("Currently Playing")] + CurrentlyPlaying } } diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index a2490365e4..787a4985d7 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -130,6 +130,11 @@ namespace osu.Game.Overlays loadDisplay(new FriendDisplay()); break; + case DashboardOverlayTabs.CurrentlyPlaying: + //todo: enable once caching logic is better + //loadDisplay(new CurrentlyPlayingDisplay()); + break; + default: throw new NotImplementedException($"Display for {tab.NewValue} tab is not implemented"); } diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs index 6461bd7b93..8134c350a6 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs @@ -27,6 +27,12 @@ namespace osu.Game.Overlays.Settings.Sections.Online Keywords = new[] { "no-video" }, Current = config.GetBindable(OsuSetting.PreferNoVideo) }, + new SettingsCheckbox + { + LabelText = "Automatically download beatmaps when spectating", + Keywords = new[] { "spectator" }, + Current = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating), + }, }; } } diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 61605d9e9e..7798dfa576 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -104,7 +106,7 @@ namespace osu.Game.Overlays public OverlayHeaderTabItem(T value) : base(value) { - Text.Text = value.ToString().ToLower(); + Text.Text = ((Value as Enum)?.GetDescription() ?? Value.ToString()).ToLower(); Text.Font = OsuFont.GetFont(size: 14); Text.Margin = new MarginPadding { Vertical = 16.5f }; // 15px padding + 1.5px line-height difference compensation Bar.Margin = new MarginPadding { Bottom = bar_height }; diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index 2f65dc06d0..9ed911efd5 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -9,20 +9,26 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Spectator; using osu.Game.Overlays.BeatmapListing.Panels; +using osu.Game.Overlays.Settings; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; +using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; using osuTK; @@ -63,6 +69,12 @@ namespace osu.Game.Screens.Play private IBindable> managerUpdated; + private TriangleButton watchButton; + + private SettingsCheckbox automaticDownload; + + private BeatmapSetInfo onlineBeatmap; + /// /// Becomes true if a new state is waiting to be loaded (while this screen was not active). /// @@ -74,47 +86,91 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours, OsuConfigManager config) { - InternalChildren = new Drawable[] + InternalChild = new Container { - new FillFlowContainer + Masking = true, + CornerRadius = 20, + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 500, + AutoSizeEasing = Easing.OutQuint, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Spacing = new Vector2(15), - Children = new Drawable[] + new Box { - new OsuSpriteText + Colour = colours.GreySeafoamDark, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(20), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(15), + Children = new Drawable[] { - Text = "Currently spectating", - Font = OsuFont.Default.With(size: 30), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new UserGridPanel(targetUser) - { - Width = 290, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new OsuSpriteText - { - Text = "playing", - Font = OsuFont.Default.With(size: 30), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - beatmapPanelContainer = new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, + new OsuSpriteText + { + Text = "Spectator Mode", + Font = OsuFont.Default.With(size: 30), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(15), + Children = new Drawable[] + { + new UserGridPanel(targetUser) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 145, + Width = 290, + }, + new SpriteIcon + { + Size = new Vector2(40), + Icon = FontAwesome.Solid.ArrowRight, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + beatmapPanelContainer = new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } + }, + automaticDownload = new SettingsCheckbox + { + LabelText = "Automatically download beatmaps", + Current = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + watchButton = new PurpleTriangleButton + { + Text = "Start Watching", + Width = 250, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = attemptStart, + Enabled = { Value = false } + } + } } - }, + } }; } @@ -130,12 +186,14 @@ namespace osu.Game.Screens.Play managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); + + automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload()); } private void beatmapUpdated(ValueChangedEvent> beatmap) { if (beatmap.NewValue.TryGetTarget(out var beatmapSet) && beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == state.BeatmapID)) - attemptStart(); + Schedule(attemptStart); } private void userSentFrames(int userId, FrameDataBundle data) @@ -189,14 +247,26 @@ namespace osu.Game.Screens.Play if (userId != targetUser.Id) return; - if (replay == null) return; + if (replay != null) + { + replay.HasReceivedAllFrames = true; + replay = null; + } - replay.HasReceivedAllFrames = true; - replay = null; + Schedule(clearDisplay); + } + + private void clearDisplay() + { + watchButton.Enabled.Value = false; + beatmapPanelContainer.Clear(); } private void attemptStart() { + clearDisplay(); + showBeatmapPanel(state); + var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == state.RulesetID)?.CreateInstance(); // ruleset not available @@ -210,7 +280,6 @@ namespace osu.Game.Screens.Play if (resolvedBeatmap == null) { - showBeatmapPanel(state.BeatmapID.Value); return; } @@ -228,6 +297,7 @@ namespace osu.Game.Screens.Play rulesetInstance = resolvedRuleset; beatmap.Value = beatmaps.GetWorkingBeatmap(resolvedBeatmap); + watchButton.Enabled.Value = true; this.Push(new SpectatorPlayerLoader(new Score { @@ -236,17 +306,43 @@ namespace osu.Game.Screens.Play })); } - private void showBeatmapPanel(int beatmapId) + private void showBeatmapPanel(SpectatorState state) { - var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); + if (state?.BeatmapID == null) + { + beatmapPanelContainer.Clear(); + onlineBeatmap = null; + return; + } + + var req = new GetBeatmapSetRequest(state.BeatmapID.Value, BeatmapSetLookupType.BeatmapId); req.Success += res => Schedule(() => { - beatmapPanelContainer.Child = new GridBeatmapPanel(res.ToBeatmapSet(rulesets)); + if (state != this.state) + return; + + onlineBeatmap = res.ToBeatmapSet(rulesets); + beatmapPanelContainer.Child = new GridBeatmapPanel(onlineBeatmap); + checkForAutomaticDownload(); }); api.Queue(req); } + private void checkForAutomaticDownload() + { + if (onlineBeatmap == null) + return; + + if (!automaticDownload.Current.Value) + return; + + if (beatmaps.IsAvailableLocally(onlineBeatmap)) + return; + + beatmaps.Download(onlineBeatmap); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 6c1e83f236..fdf996150f 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -3,33 +3,60 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; using osu.Game.Scoring; +using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play { - public class SpectatorPlayer : ReplayPlayer + public class SpectatorPlayer : Player { - [Resolved] - private SpectatorStreamingClient spectatorStreaming { get; set; } + private readonly Score score; + + protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap public SpectatorPlayer(Score score) - : base(score) { + this.score = score; } + protected override ResultsScreen CreateResults(ScoreInfo score) + { + return new SpectatorResultsScreen(score); + } + + [Resolved] + private SpectatorStreamingClient spectatorStreaming { get; set; } + [BackgroundDependencyLoader] private void load() { spectatorStreaming.OnUserBeganPlaying += userBeganPlaying; + + AddInternal(new OsuSpriteText + { + Text = $"Watching {score.ScoreInfo.User.Username} playing live!", + Font = OsuFont.Default.With(size: 30), + Y = 100, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }); + } + + protected override void PrepareReplay() + { + DrawableRuleset?.SetReplayScore(score); } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) { // if we already have frames, start gameplay at the point in time they exist, should they be too far into the beatmap. - double? firstFrameTime = Score.Replay.Frames.FirstOrDefault()?.Time; + double? firstFrameTime = score.Replay.Frames.FirstOrDefault()?.Time; if (firstFrameTime == null || firstFrameTime <= gameplayStart + 5000) return base.CreateGameplayClockContainer(beatmap, gameplayStart); @@ -43,6 +70,16 @@ namespace osu.Game.Screens.Play return base.OnExiting(next); } + private void userBeganPlaying(int userId, SpectatorState state) + { + if (userId != score.ScoreInfo.UserID) return; + + Schedule(() => + { + if (this.IsCurrentScreen()) this.Exit(); + }); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -50,11 +87,5 @@ namespace osu.Game.Screens.Play if (spectatorStreaming != null) spectatorStreaming.OnUserBeganPlaying -= userBeganPlaying; } - - private void userBeganPlaying(int userId, SpectatorState state) - { - if (userId == Score.ScoreInfo.UserID) - Schedule(this.Exit); - } } } diff --git a/osu.Game/Screens/Play/SpectatorResultsScreen.cs b/osu.Game/Screens/Play/SpectatorResultsScreen.cs new file mode 100644 index 0000000000..56ccfd2253 --- /dev/null +++ b/osu.Game/Screens/Play/SpectatorResultsScreen.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Game.Online.Spectator; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Screens.Play +{ + public class SpectatorResultsScreen : SoloResultsScreen + { + public SpectatorResultsScreen(ScoreInfo score) + : base(score) + { + } + + [Resolved] + private SpectatorStreamingClient spectatorStreaming { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + spectatorStreaming.OnUserBeganPlaying += userBeganPlaying; + } + + private void userBeganPlaying(int userId, SpectatorState state) + { + if (userId == Score.UserID) + { + Schedule(() => + { + if (this.IsCurrentScreen()) this.Exit(); + }); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (spectatorStreaming != null) + spectatorStreaming.OnUserBeganPlaying -= userBeganPlaying; + } + } +} diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 57a87a713d..0981136dba 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -20,6 +20,10 @@ namespace osu.Game.Users { public readonly User User; + /// + /// Perform an action in addition to showing the user's profile. + /// This should be used to perform auxiliary tasks and not as a primary action for clicking a user panel (to maintain a consistent UX). + /// public new Action Action; protected Action ViewProfile { get; private set; }