From 19f39ca1b681c8ce0cd8742814458a1faefb9d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 11:51:09 +0200 Subject: [PATCH 1/9] Extract `OnlinePlayScreenWaveContainer` from `OnlinePlayScreen` --- .../Screens/OnlinePlay/OnlinePlayScreen.cs | 18 ++------------- .../OnlinePlayScreenWaveContainer.cs | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 9de458b5c6..9b6284fb89 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Screens; @@ -36,7 +35,7 @@ namespace osu.Game.Screens.OnlinePlay protected LoungeSubScreen Lounge { get; private set; } - private MultiplayerWaveContainer waves; + private OnlinePlayScreenWaveContainer waves; private ScreenStack screenStack; [Cached(Type = typeof(IRoomManager))] @@ -63,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay [BackgroundDependencyLoader] private void load() { - InternalChild = waves = new MultiplayerWaveContainer + InternalChild = waves = new OnlinePlayScreenWaveContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -230,19 +229,6 @@ namespace osu.Game.Screens.OnlinePlay protected abstract LoungeSubScreen CreateLounge(); - private partial class MultiplayerWaveContainer : WaveContainer - { - protected override bool StartHidden => true; - - public MultiplayerWaveContainer() - { - FirstWaveColour = Color4Extensions.FromHex(@"654d8c"); - SecondWaveColour = Color4Extensions.FromHex(@"554075"); - ThirdWaveColour = Color4Extensions.FromHex(@"44325e"); - FourthWaveColour = Color4Extensions.FromHex(@"392850"); - } - } - ScreenStack IHasSubScreenStack.SubScreenStack => screenStack; } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs new file mode 100644 index 0000000000..bfa68d82cd --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.OnlinePlay +{ + public partial class OnlinePlayScreenWaveContainer : WaveContainer + { + protected override bool StartHidden => true; + + public OnlinePlayScreenWaveContainer() + { + FirstWaveColour = Color4Extensions.FromHex(@"654d8c"); + SecondWaveColour = Color4Extensions.FromHex(@"554075"); + ThirdWaveColour = Color4Extensions.FromHex(@"44325e"); + FourthWaveColour = Color4Extensions.FromHex(@"392850"); + } + } +} From d80f09e0c0f90b163c3f271e75cdcfc97abd8e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 12:02:16 +0200 Subject: [PATCH 2/9] Adjust online play header to be reusable for new daily challenge screen --- osu.Game/Screens/OnlinePlay/Header.cs | 34 +++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Header.cs b/osu.Game/Screens/OnlinePlay/Header.cs index 4c4851c3ac..860042fd37 100644 --- a/osu.Game/Screens/OnlinePlay/Header.cs +++ b/osu.Game/Screens/OnlinePlay/Header.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Humanizer; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -20,10 +18,10 @@ namespace osu.Game.Screens.OnlinePlay { public const float HEIGHT = 80; - private readonly ScreenStack stack; + private readonly ScreenStack? stack; private readonly MultiHeaderTitle title; - public Header(string mainTitle, ScreenStack stack) + public Header(LocalisableString mainTitle, ScreenStack? stack) { this.stack = stack; @@ -37,12 +35,15 @@ namespace osu.Game.Screens.OnlinePlay Origin = Anchor.CentreLeft, }; - // unnecessary to unbind these as this header has the same lifetime as the screen stack we are attaching to. - stack.ScreenPushed += (_, _) => updateSubScreenTitle(); - stack.ScreenExited += (_, _) => updateSubScreenTitle(); + if (stack != null) + { + // unnecessary to unbind these as this header has the same lifetime as the screen stack we are attaching to. + stack.ScreenPushed += (_, _) => updateSubScreenTitle(); + stack.ScreenExited += (_, _) => updateSubScreenTitle(); + } } - private void updateSubScreenTitle() => title.Screen = stack.CurrentScreen as IOnlinePlaySubScreen; + private void updateSubScreenTitle() => title.Screen = stack?.CurrentScreen as IOnlinePlaySubScreen; private partial class MultiHeaderTitle : CompositeDrawable { @@ -51,13 +52,16 @@ namespace osu.Game.Screens.OnlinePlay private readonly OsuSpriteText dot; private readonly OsuSpriteText pageTitle; - [CanBeNull] - public IOnlinePlaySubScreen Screen + public IOnlinePlaySubScreen? Screen { - set => pageTitle.Text = value?.ShortTitle.Titleize() ?? string.Empty; + set + { + pageTitle.Text = value?.ShortTitle.Titleize() ?? default(LocalisableString); + dot.Alpha = pageTitle.Text == default ? 0 : 1; + } } - public MultiHeaderTitle(string mainTitle) + public MultiHeaderTitle(LocalisableString mainTitle) { AutoSizeAxes = Axes.Both; @@ -82,14 +86,14 @@ namespace osu.Game.Screens.OnlinePlay Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.TorusAlternate.With(size: 24), - Text = "·" + Text = "·", + Alpha = 0, }, pageTitle = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.TorusAlternate.With(size: 24), - Text = "Lounge" } } }, From f135a9a923ce2128c9fd467e85711c39ada22afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 13:49:47 +0200 Subject: [PATCH 3/9] Make `SelectedItem` externally mutable Not being able to externally mutate this was making reuse in new daily challenge screen unnecessarily arduous. --- osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index ceb8e53778..45f52f3cd8 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online.Rooms /// public partial class OnlinePlayBeatmapAvailabilityTracker : CompositeComponent { - public readonly IBindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); [Resolved] private RealmAccess realm { get; set; } = null!; From dd6e9308b3c85bfd9dc6e9269035ce9340d65145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 13:50:56 +0200 Subject: [PATCH 4/9] Extract user mod select button for reuse --- .../Multiplayer/TestSceneMultiplayer.cs | 2 +- .../TestSceneMultiplayerMatchSubScreen.cs | 4 +-- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 19 ------------- .../OnlinePlay/Match/UserModSelectButton.cs | 27 +++++++++++++++++++ 4 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 8c7576ff52..3306b6624e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -644,7 +644,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddStep("open mod overlay", () => this.ChildrenOfType().Single().TriggerClick()); + AddStep("open mod overlay", () => this.ChildrenOfType().Single().TriggerClick()); AddStep("invoke on back button", () => multiplayerComponents.OnBackButton()); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index bdfe01ba09..f9ef085838 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -197,7 +197,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("mod select contents loaded", () => this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded)); @@ -311,7 +311,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddAssert("mod select shows unranked", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().Ranked.Value == false); AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 97fbb83992..a694faebac 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -17,12 +17,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Overlays; @@ -531,22 +528,6 @@ namespace osu.Game.Screens.OnlinePlay.Match /// The room to change the settings of. protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room); - public partial class UserModSelectButton : PurpleRoundedButton, IKeyBindingHandler - { - public bool OnPressed(KeyBindingPressEvent e) - { - if (e.Action == GlobalAction.ToggleModSelection && !e.Repeat) - { - TriggerClick(); - return true; - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) { } - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.cs b/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.cs new file mode 100644 index 0000000000..f3ea82be99 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Input.Bindings; +using osu.Game.Screens.OnlinePlay.Match.Components; + +namespace osu.Game.Screens.OnlinePlay.Match +{ + public partial class UserModSelectButton : PurpleRoundedButton, IKeyBindingHandler + { + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == GlobalAction.ToggleModSelection && !e.Repeat) + { + TriggerClick(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) { } + } +} From ffd788c65cbb0794d0dcdba88bc1ff7c2595c5c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 14:15:23 +0200 Subject: [PATCH 5/9] Use room mod select overlay rely on explicit binding rather than DI resolution --- osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs | 5 ++--- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index 55e077df0f..6a856d8d72 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs @@ -16,8 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Match { public partial class RoomModSelectOverlay : UserModSelectOverlay { - [Resolved] - private IBindable selectedItem { get; set; } = null!; + public Bindable SelectedItem { get; } = new Bindable(); [Resolved] private RulesetStore rulesets { get; set; } = null!; @@ -33,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.LoadComplete(); - selectedItem.BindValueChanged(v => + SelectedItem.BindValueChanged(v => { roomRequiredMods.Clear(); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index a694faebac..4eb092d08b 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -240,6 +240,7 @@ namespace osu.Game.Screens.OnlinePlay.Match LoadComponent(UserModsSelectOverlay = new RoomModSelectOverlay { + SelectedItem = { BindTarget = SelectedItem }, SelectedMods = { BindTarget = UserMods }, IsValidMod = _ => false }); 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 6/9] 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(); + } + } +} From 2321e408cb017d65be8e4dea890ad407f7cbb1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 Jun 2024 15:36:22 +0200 Subject: [PATCH 7/9] Add test scene for daily challenge screen --- .../DailyChallenge/TestSceneDailyChallenge.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs new file mode 100644 index 0000000000..cd09a1d20f --- /dev/null +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -0,0 +1,40 @@ +// 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.Linq; +using NUnit.Framework; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.DailyChallenge +{ + public partial class TestSceneDailyChallenge : OnlinePlayTestScene + { + [Test] + public void TestDailyChallenge() + { + var room = new Room + { + RoomID = { Value = 1234 }, + Name = { Value = "Daily Challenge: June 4, 2024" }, + Playlist = + { + new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + Category = { Value = RoomCategory.DailyChallenge } + }; + + AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); + AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); + } + } +} From 073ddcebe4da0a447bfe2aaeb9ea5d8514a746c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 14:20:16 +0200 Subject: [PATCH 8/9] Hide daily challenge from playlists listing --- .../Screens/OnlinePlay/Components/ListingPollingComponent.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index c296e2a86b..4b38ea68b3 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -53,6 +53,8 @@ namespace osu.Game.Screens.OnlinePlay.Components req.Success += result => { + result = result.Where(r => r.Category.Value != RoomCategory.DailyChallenge).ToList(); + foreach (var existing in RoomManager.Rooms.ToArray()) { if (result.All(r => r.RoomID.Value != existing.RoomID.Value)) From 5e002fbf9b8af739189bad1a12e4ab288375e640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 08:59:50 +0200 Subject: [PATCH 9/9] Fix user mod select button being inserted in incorrect place --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index bff58dbb58..5740825ca2 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -257,7 +257,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge if (playlistItem.AllowedMods.Any()) { - footerButtons.Insert(0, new UserModSelectButton + footerButtons.Insert(-1, new UserModSelectButton { Text = "Free mods", Anchor = Anchor.Centre,