From e6da17d24865ecfe35d2f7959c491a9a274f3ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 14:15:44 +0200 Subject: [PATCH] Add minimal viable variant of new daily challenge screen --- osu.Game/Screens/Menu/MainMenu.cs | 5 +- .../DailyChallenge/DailyChallenge.cs | 376 ++++++++++++++++++ 2 files changed, 378 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 722e776e3d..00b9d909a1 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -31,6 +31,7 @@ using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; +using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Select; @@ -149,9 +150,7 @@ namespace osu.Game.Screens.Menu OnPlaylists = () => this.Push(new Playlists()), OnDailyChallenge = room => { - Playlists playlistsScreen; - this.Push(playlistsScreen = new Playlists()); - playlistsScreen.Join(room); + this.Push(new DailyChallenge(room)); }, OnExit = () => { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs new file mode 100644 index 0000000000..bff58dbb58 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -0,0 +1,376 @@ +// 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 System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Localisation; +using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.OnlinePlay.Match; +using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Screens.Play; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class DailyChallenge : OsuScreen + { + private readonly Room room; + private readonly PlaylistItem playlistItem; + + /// + /// Any mods applied by/to the local user. + /// + private readonly Bindable> userMods = new Bindable>(Array.Empty()); + + private OnlinePlayScreenWaveContainer waves = null!; + private MatchLeaderboard leaderboard = null!; + private RoomModSelectOverlay userModsSelectOverlay = null!; + private Sample? sampleStart; + private IDisposable? userModsSelectOverlayRegistration; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + + [Cached(Type = typeof(IRoomManager))] + private RoomManager roomManager { get; set; } + + [Cached] + private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + + [Resolved] + private BeatmapManager beatmapManager { get; set; } = null!; + + [Resolved] + private RulesetStore rulesets { get; set; } = null!; + + [Resolved] + private MusicController musicController { get; set; } = null!; + + [Resolved] + private IOverlayManager? overlayManager { get; set; } + + public override bool DisallowExternalBeatmapRulesetChanges => true; + + public DailyChallenge(Room room) + { + this.room = room; + playlistItem = room.Playlist.Single(); + roomManager = new RoomManager(); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) + { + Model = { Value = room } + }; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection"); + + FillFlowContainer footerButtons; + + InternalChild = waves = new OnlinePlayScreenWaveContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + roomManager, + beatmapAvailabilityTracker, + new ScreenStack(new RoomBackgroundScreen(playlistItem)) + { + RelativeSizeAxes = Axes.Both, + }, + new Header(ButtonSystemStrings.DailyChallenge.ToSentence(), null), + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = WaveOverlayContainer.WIDTH_PADDING, + Top = Header.HEIGHT, + }, + RowDimensions = + [ + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 30), + new Dimension(GridSizeMode.Absolute, 50) + ], + Content = new[] + { + new Drawable[] + { + new DrawableRoomPlaylistItem(playlistItem) + { + RelativeSizeAxes = Axes.X, + AllowReordering = false, + Scale = new Vector2(1.4f), + Width = 1 / 1.4f, + } + }, + null, + [ + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex(@"3e3a44") // Temporary. + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + ColumnDimensions = + [ + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension() + ], + Content = new[] + { + new Drawable?[] + { + null, + null, + // Middle column (leaderboard) + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new OverlinedHeader("Leaderboard") + }, + [leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }], + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + }, + // Spacer + null, + // Main right column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new OverlinedHeader("Chat") + }, + [new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }] + }, + RowDimensions = + [ + new Dimension(GridSizeMode.AutoSize), + new Dimension() + ] + }, + } + } + } + } + } + ], + null, + [ + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = -WaveOverlayContainer.WIDTH_PADDING, + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex(@"28242d") // Temporary. + }, + footerButtons = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(5), + Spacing = new Vector2(10), + Children = new Drawable[] + { + new PlaylistsReadyButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(250, 1), + Action = startPlay + } + } + }, + } + } + ], + } + } + } + }; + + LoadComponent(userModsSelectOverlay = new RoomModSelectOverlay + { + SelectedMods = { BindTarget = userMods }, + IsValidMod = _ => false + }); + + if (playlistItem.AllowedMods.Any()) + { + footerButtons.Insert(0, new UserModSelectButton + { + Text = "Free mods", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(250, 1), + Action = () => userModsSelectOverlay.Show(), + }); + + var rulesetInstance = rulesets.GetRuleset(playlistItem.RulesetID)!.CreateInstance(); + var allowedMods = playlistItem.AllowedMods.Select(m => m.ToMod(rulesetInstance)); + userModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; + beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true); + + userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay); + userModsSelectOverlay.SelectedItem.Value = playlistItem; + userMods.BindValueChanged(_ => Scheduler.AddOnce(updateMods), true); + } + + private void trySetDailyChallengeBeatmap() + { + var beatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == playlistItem.Beatmap.OnlineID); + Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. + Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID); + } + + public override void OnEntering(ScreenTransitionEvent e) + { + base.OnEntering(e); + + waves.Show(); + roomManager.JoinRoom(room); + applyLoopingToTrack(); + } + + public override void OnResuming(ScreenTransitionEvent e) + { + base.OnResuming(e); + applyLoopingToTrack(); + } + + public override void OnSuspending(ScreenTransitionEvent e) + { + base.OnSuspending(e); + + userModsSelectOverlay.Hide(); + cancelTrackLooping(); + } + + public override bool OnExiting(ScreenExitEvent e) + { + waves.Hide(); + userModsSelectOverlay.Hide(); + cancelTrackLooping(); + this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); + + roomManager.PartRoom(); + + return base.OnExiting(e); + } + + private void applyLoopingToTrack() + { + if (!this.IsCurrentScreen()) + return; + + var track = Beatmap.Value?.Track; + + if (track != null) + { + Beatmap.Value?.PrepareTrackForPreview(true); + musicController.EnsurePlayingSomething(); + } + } + + private void cancelTrackLooping() + { + var track = Beatmap.Value?.Track; + + if (track != null) + track.Looping = false; + } + + private void updateMods() + { + if (!this.IsCurrentScreen()) + return; + + Mods.Value = userMods.Value.Concat(playlistItem.RequiredMods.Select(m => m.ToMod(Ruleset.Value.CreateInstance()))).ToList(); + } + + private void startPlay() + { + sampleStart?.Play(); + this.Push(new PlayerLoader(() => new PlaylistsPlayer(room, playlistItem) + { + Exited = () => leaderboard.RefetchScores() + })); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + userModsSelectOverlayRegistration?.Dispose(); + } + } +}