From 34180c62eb7d4f46131d4f3763f108aebf17de6a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Apr 2025 17:47:39 +0900 Subject: [PATCH 1/3] Add display to show completed playlist items --- .../TestSceneDrawableRoomPlaylist.cs | 13 +++ .../DrawableRoomPlaylistItemStrings.cs | 19 ++++ osu.Game/Online/Rooms/ItemAttemptsCount.cs | 12 +++ osu.Game/Online/Rooms/PlaylistItem.cs | 7 ++ .../OnlinePlay/DrawableRoomPlaylistItem.cs | 90 ++++++++++++++++++- .../Playlists/PlaylistsRoomSubScreen.cs | 25 ++++++ 6 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Localisation/DrawableRoomPlaylistItemStrings.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 18cd720bf2..7e19f45a00 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -105,6 +105,19 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("no item selected", () => playlist.SelectedItem.Value == null); } + [Test] + public void TestMarkCompleted() + { + createPlaylist(); + AddStep("mark some items as complete", () => + { + playlist.Items[0].MarkCompleted(); + playlist.Items[2].MarkCompleted(); + playlist.Items[3].MarkCompleted(); + playlist.Items[5].MarkCompleted(); + }); + } + [Test] public void TestSelectable() { diff --git a/osu.Game/Localisation/DrawableRoomPlaylistItemStrings.cs b/osu.Game/Localisation/DrawableRoomPlaylistItemStrings.cs new file mode 100644 index 0000000000..44616c03ca --- /dev/null +++ b/osu.Game/Localisation/DrawableRoomPlaylistItemStrings.cs @@ -0,0 +1,19 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class DrawableRoomPlaylistItemStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.DrawableRoomPlaylistItem"; + + /// + /// "You have completed this beatmap" + /// + public static LocalisableString CompletedTooltip => new TranslatableString(getKey(@"completed_tooltip"), @"You have completed this beatmap"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Online/Rooms/ItemAttemptsCount.cs b/osu.Game/Online/Rooms/ItemAttemptsCount.cs index dc86897660..17b7f093f4 100644 --- a/osu.Game/Online/Rooms/ItemAttemptsCount.cs +++ b/osu.Game/Online/Rooms/ItemAttemptsCount.cs @@ -10,10 +10,22 @@ namespace osu.Game.Online.Rooms /// public class ItemAttemptsCount { + /// + /// The playlist item this object describes. + /// [JsonProperty("id")] public int PlaylistItemID { get; set; } + /// + /// The number of times the user attempted the playlist item. + /// [JsonProperty("attempts")] public int Attempts { get; set; } + + /// + /// Whether the user has a passing score on the playlist item. + /// + [JsonProperty("completed")] + public bool Completed { get; set; } } } diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 427f31fc64..8ba62fd0e2 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -85,6 +85,11 @@ namespace osu.Game.Online.Rooms private readonly Bindable valid = new BindableBool(true); + [JsonIgnore] + public IBindable Completed => completed; + + private readonly Bindable completed = new BindableBool(false); + [JsonConstructor] private PlaylistItem() : this(new APIBeatmap()) @@ -118,6 +123,8 @@ namespace osu.Game.Online.Rooms public void MarkInvalid() => valid.Value = false; + public void MarkCompleted() => completed.Value = true; + #region Newtonsoft.Json implicit ShouldSerialize() methods // The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases. diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 9e585d584d..0afeaa9532 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -31,13 +31,14 @@ using osu.Game.Online.Chat; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; -using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; using osu.Game.Users.Drawables; using osuTK; using osuTK.Graphics; +using osu.Game.Localisation; +using WebLocalisation = osu.Game.Resources.Localisation.Web; namespace osu.Game.Screens.OnlinePlay { @@ -76,6 +77,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly DelayedLoadWrapper onScreenLoader; private readonly IBindable valid = new Bindable(); + private readonly IBindable completed = new Bindable(); private IBeatmapInfo? beatmap; private IRulesetInfo? ruleset; @@ -128,6 +130,7 @@ namespace osu.Game.Screens.OnlinePlay Item = item; valid.BindTo(item.Valid); + completed.BindTo(item.Completed); } [BackgroundDependencyLoader] @@ -525,9 +528,27 @@ namespace osu.Game.Screens.OnlinePlay private IEnumerable createButtons() => new[] { - beatmap == null ? Empty() : new PlaylistDownloadButton(beatmap), + new CompletionIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Visible = { BindTarget = completed } + }, + beatmap == null + ? Empty().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + }) + : new PlaylistDownloadButton(beatmap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, showResultsButton = new GrayButton(FontAwesome.Solid.ChartPie) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(30, 30), Action = () => RequestResults?.Invoke(Item), Alpha = AllowShowingResults ? 1 : 0, @@ -535,13 +556,17 @@ namespace osu.Game.Screens.OnlinePlay }, editButton = new PlaylistEditButton { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(30, 30), Alpha = AllowEditing ? 1 : 0, Action = () => RequestEdit?.Invoke(Item), - TooltipText = CommonStrings.ButtonsEdit + TooltipText = WebLocalisation.CommonStrings.ButtonsEdit }, removeButton = new PlaylistRemoveButton { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(30, 30), Alpha = AllowDeletion ? 1 : 0, Action = () => RequestDeletion?.Invoke(Item), @@ -768,5 +793,64 @@ namespace osu.Game.Screens.OnlinePlay this.allowInteraction = allowInteraction; } } + + private partial class CompletionIcon : CompositeDrawable, IHasTooltip + { + public readonly BindableBool Visible = new BindableBool(); + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(16), + Masking = true, + Colour = colours.Lime0, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.5f), + Colour = OsuColour.Gray(0.5f), + Icon = FontAwesome.Solid.Check + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Visible.BindValueChanged(onVisibleChanged, true); + } + + private void onVisibleChanged(ValueChangedEvent visible) + { + if (visible.NewValue) + { + Size = new Vector2(16); + Alpha = 1; + } + else + { + Size = Vector2.Zero; + Alpha = 0; + } + } + + public LocalisableString TooltipText => DrawableRoomPlaylistItemStrings.CompletedTooltip; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 053e3b97af..47219e42cb 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -465,6 +465,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }); updateSetupState(); + updateUserScore(); updateGameplayState(); } @@ -480,6 +481,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.RoomID): updateSetupState(); break; + + case nameof(Room.UserScore): + updateUserScore(); + break; } } @@ -507,12 +512,32 @@ namespace osu.Game.Screens.OnlinePlay.Playlists progressSection.Alpha = room.MaxAttempts != null ? 1 : 0; drawablePlaylist.Items.ReplaceRange(0, drawablePlaylist.Items.Count, room.Playlist); + updateUserScore(); + // Select an initial item for the user to help them get into a playable state quicker. SelectedItem.Value = room.Playlist.FirstOrDefault(); }); } } + /// + /// Responds to changes in to mark playlist items as completed. + /// + private void updateUserScore() + { + if (room.UserScore == null) + return; + + if (drawablePlaylist.Items.Count == 0) + return; + + foreach (var item in room.UserScore.PlaylistItemAttempts) + { + if (item.Completed) + drawablePlaylist.Items.Single(i => i.ID == item.PlaylistItemID).MarkCompleted(); + } + } + /// /// Adjusts the rate at which the is updated. /// From 3dc8a4c1ed26e5cb95d146bcccb333eb9e7d09ae Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 10 Apr 2025 18:44:44 +0900 Subject: [PATCH 2/3] Rename to `passed` --- osu.Game/Online/Rooms/ItemAttemptsCount.cs | 4 ++-- .../Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Rooms/ItemAttemptsCount.cs b/osu.Game/Online/Rooms/ItemAttemptsCount.cs index 17b7f093f4..9ea2235500 100644 --- a/osu.Game/Online/Rooms/ItemAttemptsCount.cs +++ b/osu.Game/Online/Rooms/ItemAttemptsCount.cs @@ -25,7 +25,7 @@ namespace osu.Game.Online.Rooms /// /// Whether the user has a passing score on the playlist item. /// - [JsonProperty("completed")] - public bool Completed { get; set; } + [JsonProperty("passed")] + public bool Passed { get; set; } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 47219e42cb..9834598ac0 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -533,7 +533,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists foreach (var item in room.UserScore.PlaylistItemAttempts) { - if (item.Completed) + if (item.Passed) drawablePlaylist.Items.Single(i => i.ID == item.PlaylistItemID).MarkCompleted(); } } From 6fe1695d39d4c9f774807ad3bc8623c45587d099 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Apr 2025 19:40:47 +0900 Subject: [PATCH 3/3] Use full namespace isntead of weird using statement --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 0afeaa9532..85a87b0dff 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -38,7 +38,6 @@ using osu.Game.Users.Drawables; using osuTK; using osuTK.Graphics; using osu.Game.Localisation; -using WebLocalisation = osu.Game.Resources.Localisation.Web; namespace osu.Game.Screens.OnlinePlay { @@ -561,7 +560,7 @@ namespace osu.Game.Screens.OnlinePlay Size = new Vector2(30, 30), Alpha = AllowEditing ? 1 : 0, Action = () => RequestEdit?.Invoke(Item), - TooltipText = WebLocalisation.CommonStrings.ButtonsEdit + TooltipText = Resources.Localisation.Web.CommonStrings.ButtonsEdit }, removeButton = new PlaylistRemoveButton {