diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index a3541d957e4a..f1a2d6b5f2a3 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -2,19 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Screens; -using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Online.API; using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Tests.Resources; +using osu.Game.Screens.Menu; +using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Tests.Visual.Metadata; using osu.Game.Tests.Visual.OnlinePlay; +using osuTK.Graphics; +using osuTK.Input; using CreateRoomRequest = osu.Game.Online.Rooms.CreateRoomRequest; namespace osu.Game.Tests.Visual.DailyChallenge @@ -27,63 +29,61 @@ public partial class TestSceneDailyChallengeIntro : OnlinePlayTestScene [Cached(typeof(INotificationOverlay))] private NotificationOverlay notificationOverlay = new NotificationOverlay(); + private Room room = null!; + [BackgroundDependencyLoader] private void load() { - base.Content.Add(notificationOverlay); - base.Content.Add(metadataClient); + Add(notificationOverlay); + Add(metadataClient); + + // add button to observe for daily challenge changes and perform its logic. + Add(new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)); } [Test] - [Solo] public void TestDailyChallenge() { - var room = new Room - { - RoomID = { Value = 1234 }, - Name = { Value = "Daily Challenge: June 4, 2024" }, - Playlist = - { - new PlaylistItem(CreateAPIBeatmapSet().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.DailyChallengeIntro(room))); + startChallenge(1234); + AddStep("push screen", () => LoadScreen(new DailyChallengeIntro(room))); } [Test] - public void TestNotifications() + public void TestPlayIntroOnceFlag() + { + startChallenge(1234); + AddStep("set intro played flag", () => Dependencies.Get().SetValue(Static.DailyChallengeIntroPlayed, true)); + + startChallenge(1235); + + AddAssert("intro played flag reset", () => Dependencies.Get().Get(Static.DailyChallengeIntroPlayed), () => Is.False); + + AddStep("push screen", () => LoadScreen(new DailyChallengeIntro(room))); + AddUntilStep("intro played flag set", () => Dependencies.Get().Get(Static.DailyChallengeIntroPlayed), () => Is.True); + } + + private void startChallenge(int roomId) { - var room = new Room + AddStep("add room", () => { - RoomID = { Value = 1234 }, - Name = { Value = "Daily Challenge: June 4, 2024" }, - Playlist = + API.Perform(new CreateRoomRequest(room = new Room { - new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) + RoomID = { Value = roomId }, + Name = { Value = "Daily Challenge: June 4, 2024" }, + Playlist = { - 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("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); - - Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!; - AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); - AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); - AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); + new PlaylistItem(CreateAPIBeatmap(new OsuRuleset().RulesetInfo)) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + StartDate = { Value = DateTimeOffset.Now }, + EndDate = { Value = DateTimeOffset.Now.AddHours(24) }, + Category = { Value = RoomCategory.DailyChallenge } + })); + }); + AddStep("signal client", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = roomId })); } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 98f2b129ff4a..41543669ebbe 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -103,15 +103,79 @@ public void TestDailyChallengeButton() foreach (var notification in notificationOverlay.AllNotifications) notification.Close(runFlingAnimation: false); }); + AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); AddStep("hide button's parent", () => buttonContainer.Hide()); + AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = 1234, })); AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); } + + [Test] + public void TestDailyChallengeButtonOldChallenge() + { + AddStep("set up API", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case GetRoomRequest getRoomRequest: + if (getRoomRequest.RoomId != 1234) + return false; + + var beatmap = CreateAPIBeatmap(); + beatmap.OnlineID = 1001; + getRoomRequest.TriggerSuccess(new Room + { + RoomID = { Value = 1234 }, + Playlist = + { + new PlaylistItem(beatmap) + }, + StartDate = { Value = DateTimeOffset.Now.AddMinutes(-50) }, + EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) } + }); + return true; + + default: + return false; + } + }); + + NotificationOverlay notificationOverlay = null!; + + AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); + AddStep("add content", () => + { + notificationOverlay = new NotificationOverlay(); + Children = new Drawable[] + { + notificationOverlay, + new DependencyProvidingContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)], + Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ButtonSystemState = ButtonSystemState.TopLevel, + }, + }, + }; + }); + + AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo + { + RoomID = 1234 + })); + AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); + } } } diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 1548b781a704..225f20938039 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -80,5 +80,11 @@ public enum Static /// Stores the local user's last score (can be completed or aborted). /// LastLocalUserScore, + + /// + /// Whether the intro animation for the daily challenge screen has been played once. + /// This is reset when a new challenge is up. + /// + DailyChallengeIntroPlayed, } } diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index e6593c9b0d5f..4dbebf0ae9ec 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -13,6 +13,7 @@ using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps.Drawables; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; @@ -46,6 +47,9 @@ public partial class DailyChallengeButton : MainMenuButton [Resolved] private INotificationOverlay? notificationOverlay { get; set; } + [Resolved] + private SessionStatics statics { get; set; } = null!; + public DailyChallengeButton(string sampleName, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) : base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys) { @@ -128,7 +132,7 @@ protected override void Update() } } - private long? lastNotifiedDailyChallengeRoomId; + private long? lastDailyChallengeRoomID; private void dailyChallengeChanged(ValueChangedEvent _) { @@ -151,13 +155,16 @@ private void dailyChallengeChanged(ValueChangedEvent _) Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; - // We only want to notify the user if a new challenge recently went live. - if (room.StartDate.Value != null - && Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800 - && room.RoomID.Value != lastNotifiedDailyChallengeRoomId) + if (room.StartDate.Value != null && room.RoomID.Value != lastDailyChallengeRoomID) { - lastNotifiedDailyChallengeRoomId = room.RoomID.Value; - notificationOverlay?.Post(new NewDailyChallengeNotification(room)); + lastDailyChallengeRoomID = room.RoomID.Value; + + // new challenge is live, reset intro played static. + statics.SetValue(Static.DailyChallengeIntroPlayed, false); + + // we only want to notify the user if the new challenge just went live. + if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800) + notificationOverlay?.Post(new NewDailyChallengeNotification(room)); } updateCountdown(); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c1d502bd4116..346bdcb75117 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -148,7 +148,10 @@ private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings OnPlaylists = () => this.Push(new Playlists()), OnDailyChallenge = room => { - this.Push(new DailyChallengeIntro(room)); + if (statics.Get(Static.DailyChallengeIntroPlayed)) + this.Push(new DailyChallenge(room)); + else + this.Push(new DailyChallengeIntro(room)); }, OnExit = () => { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 0a1ac7a5a72e..7f0f26097c8a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -70,6 +70,9 @@ public partial class DailyChallengeIntro : OsuScreen [Resolved] private MusicController musicController { get; set; } = null!; + [Resolved] + private SessionStatics statics { get; set; } = null!; + private Sample? dateWindupSample; private Sample? dateImpactSample; private Sample? beatmapWindupSample; @@ -462,6 +465,8 @@ private void beginAnimation() { Schedule(() => { + statics.SetValue(Static.DailyChallengeIntroPlayed, true); + if (this.IsCurrentScreen()) this.Push(new DailyChallenge(room)); }); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index a75e18dbf346..cb05180d17c4 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -19,6 +19,7 @@ using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Beatmaps; +using osu.Game.Utils; namespace osu.Game.Tests.Visual.OnlinePlay { @@ -278,11 +279,18 @@ private Room cloneRoom(Room source) var result = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source)); Debug.Assert(result != null); - // Playlist item IDs aren't serialised. + // Playlist item IDs and beatmaps aren't serialised. if (source.CurrentPlaylistItem.Value != null) + { + result.CurrentPlaylistItem.Value = result.CurrentPlaylistItem.Value.With(new Optional(source.CurrentPlaylistItem.Value.Beatmap)); result.CurrentPlaylistItem.Value.ID = source.CurrentPlaylistItem.Value.ID; + } + for (int i = 0; i < source.Playlist.Count; i++) + { + result.Playlist[i] = result.Playlist[i].With(new Optional(source.Playlist[i].Beatmap)); result.Playlist[i].ID = source.Playlist[i].ID; + } return result; } diff --git a/osu.Game/Utils/Optional.cs b/osu.Game/Utils/Optional.cs index 301767ba08db..f5749a513f50 100644 --- a/osu.Game/Utils/Optional.cs +++ b/osu.Game/Utils/Optional.cs @@ -22,7 +22,7 @@ public readonly ref struct Optional /// public readonly bool HasValue; - private Optional(T value) + public Optional(T value) { Value = value; HasValue = true;