diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 456fb0b110..458fc1e58b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -25,6 +25,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(SpectatorStreamingClient))] private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient(); + // used just to show beatmap card for the time being. + protected override bool UseOnlineAPI => true; + private Spectator spectatorScreen; [Resolved] @@ -134,9 +137,9 @@ namespace osu.Game.Tests.Visual.Gameplay start(); sendFrames(); waitForPlayer(); + // should immediately exit and unbind from streaming client AddStep("stop spectating", () => (Stack.CurrentScreen as Player)?.Exit()); - AddUntilStep("spectating stopped", () => spectatorScreen.GetParentScreen() == null); } @@ -145,14 +148,15 @@ namespace osu.Game.Tests.Visual.Gameplay { loadSpectatingScreen(); - start(); + start(88); sendFrames(); - // player should never arrive. + + AddAssert("screen didn't change", () => Stack.CurrentScreen is Spectator); } private void waitForPlayer() => AddUntilStep("wait for player", () => Stack.CurrentScreen is Player); - private void start() => AddStep("start play", () => testSpectatorStreamingClient.StartPlay()); + private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorStreamingClient.StartPlay(beatmapId)); private void checkPaused(bool state) => AddAssert($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); @@ -176,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay public readonly User StreamingUser = new User { Id = 1234, Username = "Test user" }; - public void StartPlay() => sendState(); + public void StartPlay(int? beatmapId = null) => sendState(beatmapId); public void EndPlay() { @@ -218,12 +222,12 @@ namespace osu.Game.Tests.Visual.Gameplay base.WatchUser(userId); } - private void sendState() + private void sendState(int? beatmapId = null) { sentState = true; ((ISpectatorClient)this).UserBeganPlaying((int)StreamingUser.Id, new SpectatorState { - BeatmapID = beatmaps.GetAllUsableBeatmapSets().First().Beatmaps.First(b => b.RulesetID == 0).OnlineBeatmapID, + BeatmapID = beatmapId ?? beatmaps.GetAllUsableBeatmapSets().First().Beatmaps.First(b => b.RulesetID == 0).OnlineBeatmapID, RulesetID = 0, }); } diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index dcbf502fb2..dd6b434889 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -8,10 +8,15 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +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.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -19,6 +24,7 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; using osu.Game.Users; +using osuTK; namespace osu.Game.Screens.Play { @@ -35,6 +41,9 @@ namespace osu.Game.Screens.Play [Resolved] private Bindable> mods { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + [Resolved] private SpectatorStreamingClient spectatorStreaming { get; set; } @@ -46,6 +55,12 @@ namespace osu.Game.Screens.Play private Replay replay; + private Container beatmapPanelContainer; + + private SpectatorState state; + + private IBindable> managerUpdated; + public Spectator([NotNull] User targetUser) { this.targetUser = targetUser ?? throw new ArgumentNullException(nameof(targetUser)); @@ -56,11 +71,42 @@ namespace osu.Game.Screens.Play { InternalChildren = new Drawable[] { - new OsuSpriteText + new FillFlowContainer { - Text = $"Watching {targetUser}", + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, Anchor = Anchor.Centre, Origin = Anchor.Centre, + Spacing = new Vector2(15), + Children = new Drawable[] + { + new OsuSpriteText + { + 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, + }, + } }, }; } @@ -74,6 +120,15 @@ namespace osu.Game.Screens.Play spectatorStreaming.OnNewFrames += userSentFrames; spectatorStreaming.WatchUser((int)targetUser.Id); + + managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(beatmapUpdated); + } + + private void beatmapUpdated(ValueChangedEvent> beatmap) + { + if (beatmap.NewValue.TryGetTarget(out var beatmapSet) && beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == state.BeatmapID)) + attemptStart(); } private void userSentFrames(int userId, FrameDataBundle data) @@ -107,16 +162,31 @@ namespace osu.Game.Screens.Play replay ??= new Replay { HasReceivedAllFrames = false }; + this.state = state; + + attemptStart(); + } + + private void attemptStart() + { var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == state.RulesetID)?.CreateInstance(); // ruleset not available if (resolvedRuleset == null) return; + if (state.BeatmapID == null) + return; + + this.MakeCurrent(); + var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == state.BeatmapID); if (resolvedBeatmap == null) + { + showBeatmapPanel(state.BeatmapID.Value); return; + } var scoreInfo = new ScoreInfo { @@ -125,8 +195,6 @@ namespace osu.Game.Screens.Play Ruleset = resolvedRuleset.RulesetInfo, }; - this.MakeCurrent(); - ruleset.Value = resolvedRuleset.RulesetInfo; beatmap.Value = beatmaps.GetWorkingBeatmap(resolvedBeatmap); @@ -137,6 +205,17 @@ namespace osu.Game.Screens.Play })); } + private void showBeatmapPanel(int beatmapId) + { + var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); + req.Success += res => Schedule(() => + { + beatmapPanelContainer.Child = new GridBeatmapPanel(res.ToBeatmapSet(rulesets)); + }); + + api.Queue(req); + } + private void userFinishedPlaying(int userId, SpectatorState state) { if (replay == null) return; @@ -156,6 +235,8 @@ namespace osu.Game.Screens.Play spectatorStreaming.StopWatchingUser((int)targetUser.Id); } + + managerUpdated.UnbindAll(); } } }