From b15a54c91428cd8a0af019f6acc17c3b0a21318b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 11:36:00 +0900 Subject: [PATCH 01/52] Use new overlay pop-in/pop-out samples --- osu.Game/Overlays/ChatOverlay.cs | 3 +++ osu.Game/Overlays/WaveOverlayContainer.cs | 1 + osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs | 3 +++ 3 files changed, 7 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 87df08ceec..a47d10c565 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -55,6 +55,9 @@ namespace osu.Game.Overlays private const float side_bar_width = 190; private const float chat_bar_height = 60; + protected override string PopInSampleName => @"UI/overlay-big-pop-in"; + protected override string PopOutSampleName => @"UI/overlay-big-pop-out"; + [Resolved] private OsuConfigManager config { get; set; } = null!; diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 153f7f5412..848d9e60b7 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -19,6 +19,7 @@ namespace osu.Game.Overlays protected override bool StartHidden => true; protected override string PopInSampleName => "UI/wave-pop-in"; + protected override string PopOutSampleName => "UI/overlay-big-pop-out"; public const float HORIZONTAL_PADDING = 50; diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index 5753c268d9..7b631ebfea 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -32,6 +32,9 @@ namespace osu.Game.Screens.Select.Options public override bool BlockScreenWideMouse => false; + protected override string PopInSampleName => "SongSelect/options-pop-in"; + protected override string PopOutSampleName => "SongSelect/options-pop-out"; + public BeatmapOptionsOverlay() { AutoSizeAxes = Axes.Y; From 04a1f6a5085e0cc076abba71068d4b8d1031c0ab Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 11:37:07 +0900 Subject: [PATCH 02/52] Add panning to certain overlay pop-in/pop-outs --- .../Containers/OsuFocusedOverlayContainer.cs | 15 +++++++++++---- osu.Game/Overlays/LoginOverlay.cs | 2 ++ osu.Game/Overlays/NotificationOverlay.cs | 2 ++ osu.Game/Overlays/SettingsPanel.cs | 1 + 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index f92cfc2306..162c4b6a59 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -24,6 +24,7 @@ namespace osu.Game.Graphics.Containers private Sample samplePopOut; protected virtual string PopInSampleName => "UI/overlay-pop-in"; protected virtual string PopOutSampleName => "UI/overlay-pop-out"; + protected virtual double PopInOutSampleBalance => 0; protected override bool BlockNonPositionalInput => true; @@ -133,15 +134,21 @@ namespace osu.Game.Graphics.Containers return; } - if (didChange) - samplePopIn?.Play(); + if (didChange && samplePopIn != null) + { + samplePopIn.Balance.Value = PopInOutSampleBalance; + samplePopIn.Play(); + } if (BlockScreenWideMouse && DimMainContent) overlayManager?.ShowBlockingOverlay(this); break; case Visibility.Hidden: - if (didChange) - samplePopOut?.Play(); + if (didChange && samplePopOut != null) + { + samplePopOut.Balance.Value = PopInOutSampleBalance; + samplePopOut.Play(); + } if (BlockScreenWideMouse) overlayManager?.HideBlockingOverlay(this); break; diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index a575253e71..8a4bda89d9 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -20,6 +20,8 @@ namespace osu.Game.Overlays private const float transition_time = 400; + protected override double PopInOutSampleBalance => OsuGameBase.SFX_STEREO_STRENGTH; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index c9d09848f8..ef181830b7 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays public LocalisableString Title => NotificationsStrings.HeaderTitle; public LocalisableString Description => NotificationsStrings.HeaderDescription; + protected override double PopInOutSampleBalance => OsuGameBase.SFX_STEREO_STRENGTH; + public const float WIDTH = 320; public const float TRANSITION_LENGTH = 600; diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index d7f39a9d8f..58c56a5514 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -56,6 +56,7 @@ namespace osu.Game.Overlays private SeekLimitedSearchTextBox searchTextBox; protected override string PopInSampleName => "UI/settings-pop-in"; + protected override double PopInOutSampleBalance => -OsuGameBase.SFX_STEREO_STRENGTH; private readonly bool showSidebar; From 3d7ba0e18cdc9c739119125243c73a205e3f4fcb Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 17:03:45 +0900 Subject: [PATCH 03/52] Add pop-in/pop-out sfx to more overlays --- .../Collections/ManageCollectionsDialog.cs | 3 +++ .../Graphics/UserInterfaceV2/OsuPopover.cs | 25 ++++++++++++++++++- .../Screens/Edit/Setup/LabelledFileChooser.cs | 3 +++ .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 ++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 31016b807b..cc0f23d030 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -23,6 +23,9 @@ namespace osu.Game.Collections private AudioFilter lowPassFilter = null!; + protected override string PopInSampleName => @"UI/overlay-big-pop-in"; + protected override string PopOutSampleName => @"UI/overlay-big-pop-out"; + public ManageCollectionsDialog() { Anchor = Anchor.Centre; diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 381193d539..2aa42d0d50 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -21,6 +23,16 @@ namespace osu.Game.Graphics.UserInterfaceV2 private const float fade_duration = 250; private const double scale_duration = 500; + private Sample? samplePopIn; + private Sample? samplePopOut; + protected virtual string PopInSampleName => "UI/overlay-pop-in"; + protected virtual string PopOutSampleName => "UI/overlay-pop-out"; + + // required due to LoadAsyncComplete() calling PopOut() during load - similar workaround to `OsuDropdownMenu` + private bool wasOpened; + + protected virtual bool PlayPopInOutSamples => true; + public OsuPopover(bool withPadding = true) { Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding(); @@ -38,9 +50,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 } [BackgroundDependencyLoader(true)] - private void load(OverlayColourProvider? colourProvider, OsuColour colours) + private void load(OverlayColourProvider? colourProvider, OsuColour colours, AudioManager audio) { Background.Colour = Arrow.Colour = colourProvider?.Background4 ?? colours.GreySeaFoamDarker; + samplePopIn = audio.Samples.Get(PopInSampleName); + samplePopOut = audio.Samples.Get(PopOutSampleName); } protected override Drawable CreateArrow() => Empty(); @@ -49,12 +63,21 @@ namespace osu.Game.Graphics.UserInterfaceV2 { this.ScaleTo(1, scale_duration, Easing.OutElasticHalf); this.FadeIn(fade_duration, Easing.OutQuint); + + if (PlayPopInOutSamples) + { + samplePopIn?.Play(); + wasOpened = true; + } } protected override void PopOut() { this.ScaleTo(0.7f, scale_duration, Easing.OutQuint); this.FadeOut(fade_duration, Easing.OutQuint); + + if (wasOpened && PlayPopInOutSamples) + samplePopOut?.Play(); } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs index d14357e875..61f33c4bdc 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs @@ -114,6 +114,9 @@ namespace osu.Game.Screens.Edit.Setup private partial class FileChooserPopover : OsuPopover { + protected override string PopInSampleName => "UI/overlay-big-pop-in"; + protected override string PopOutSampleName => "UI/overlay-big-pop-out"; + public FileChooserPopover(string[] handledExtensions, Bindable currentFile, string? chooserPath) { Child = new Container diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 70e4b2a589..030408de84 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -191,6 +191,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge protected override bool BlockNonPositionalInput => true; + protected override bool PlayPopInOutSamples => false; + public PasswordEntryPopover(Room room) { this.room = room; From d10d7b6ea41f994c586725e127647b171634fae9 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 17 Aug 2023 17:07:02 +0900 Subject: [PATCH 04/52] Change some component samples to go better with overlay pop-in samples --- osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs | 4 ++-- osu.Game/Graphics/UserInterface/ShearedToggleButton.cs | 5 +++++ osu.Game/Overlays/Mods/AddPresetButton.cs | 2 ++ .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index de4df96942..0eec04541c 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -46,8 +46,8 @@ namespace osu.Game.Graphics.UserInterface private readonly Container content; private readonly Box hover; - public OsuAnimatedButton() - : base(HoverSampleSet.Button) + public OsuAnimatedButton(HoverSampleSet sampleSet = HoverSampleSet.Button) + : base(sampleSet) { base.Content.Add(content = new Container { diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index d5e0abe9d8..07ee26749a 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs @@ -14,6 +14,8 @@ namespace osu.Game.Graphics.UserInterface private Sample? sampleOff; private Sample? sampleOn; + protected virtual bool PlayClickSampleOnly => false; + /// /// Whether this button is currently toggled to an active state. /// @@ -68,6 +70,9 @@ namespace osu.Game.Graphics.UserInterface { sampleClick?.Play(); + if (PlayClickSampleOnly) + return; + if (Active.Value) sampleOn?.Play(); else diff --git a/osu.Game/Overlays/Mods/AddPresetButton.cs b/osu.Game/Overlays/Mods/AddPresetButton.cs index 731079d1d9..9063fd54f5 100644 --- a/osu.Game/Overlays/Mods/AddPresetButton.cs +++ b/osu.Game/Overlays/Mods/AddPresetButton.cs @@ -18,6 +18,8 @@ namespace osu.Game.Overlays.Mods { public partial class AddPresetButton : ShearedToggleButton, IHasPopover { + protected override bool PlayClickSampleOnly => true; + [Resolved] private OsuColour colours { get; set; } = null!; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 59b0bd1785..e36f1e9cad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -262,6 +262,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly OsuSpriteText divisorText; public DivisorDisplay() + : base(HoverSampleSet.Default) { Anchor = Anchor.Centre; Origin = Anchor.Centre; From c811546868cb8754a32049bdfbd9bc22ab0be171 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Aug 2023 21:09:50 +0900 Subject: [PATCH 05/52] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a5a9387e36..a2afcd7a9f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From fc2fac577fcdb1d329b83e2cb2e30fc5e7ea78ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Aug 2023 17:05:25 +0900 Subject: [PATCH 06/52] Inverse and xmldoc `ShearedToggleButton` sample allowance bool --- .../UserInterface/ShearedToggleButton.cs | 20 +++++++++++-------- osu.Game/Overlays/Mods/AddPresetButton.cs | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index 07ee26749a..05ed531d02 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs @@ -14,7 +14,11 @@ namespace osu.Game.Graphics.UserInterface private Sample? sampleOff; private Sample? sampleOn; - protected virtual bool PlayClickSampleOnly => false; + /// + /// Sheared toggle buttons by default play two samples when toggled: a click and a toggle (on/off). + /// Sometimes this might be too much. Setting this to false will silence the toggle sound. + /// + protected virtual bool PlayToggleSamples => true; /// /// Whether this button is currently toggled to an active state. @@ -70,13 +74,13 @@ namespace osu.Game.Graphics.UserInterface { sampleClick?.Play(); - if (PlayClickSampleOnly) - return; - - if (Active.Value) - sampleOn?.Play(); - else - sampleOff?.Play(); + if (PlayToggleSamples) + { + if (Active.Value) + sampleOn?.Play(); + else + sampleOff?.Play(); + } } } } diff --git a/osu.Game/Overlays/Mods/AddPresetButton.cs b/osu.Game/Overlays/Mods/AddPresetButton.cs index 9063fd54f5..276afd9bec 100644 --- a/osu.Game/Overlays/Mods/AddPresetButton.cs +++ b/osu.Game/Overlays/Mods/AddPresetButton.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Mods { public partial class AddPresetButton : ShearedToggleButton, IHasPopover { - protected override bool PlayClickSampleOnly => true; + protected override bool PlayToggleSamples => false; [Resolved] private OsuColour colours { get; set; } = null!; From 75750957c7cd93a2cac7d0090d146c214239eed2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Aug 2023 17:09:13 +0900 Subject: [PATCH 07/52] Add note about why pop in samples are disabled for `PasswordEntryPopover` --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 030408de84..a45583a2ec 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -191,6 +191,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge protected override bool BlockNonPositionalInput => true; + // When a room is clicked, it already plays a click sound, which clashes pretty badly with the pop in sound. + // Dunno about this one. I'd probably remove the click sound from the panel in cases they are password protected and play these pop in / out sounds. protected override bool PlayPopInOutSamples => false; public PasswordEntryPopover(Room room) From 290d18ad690accc2a54c3290e3403f6c2534fb45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 17:31:19 +0900 Subject: [PATCH 08/52] Split out difficulties in beatmap carousel in a bit of a hacky way Seems like the simplest path forward for now, without a full rewrite. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 82 ++++++++++++++++------ osu.Game/Screens/Select/FilterCriteria.cs | 5 ++ 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9af9a0ce72..d1a9b4176b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -78,6 +78,8 @@ namespace osu.Game.Screens.Select private CarouselBeatmapSet? selectedBeatmapSet; + private IEnumerable originalBeatmapSetsDetached = Enumerable.Empty(); + /// /// Raised when the is changed. /// @@ -127,13 +129,29 @@ namespace osu.Game.Screens.Select private void loadBeatmapSets(IEnumerable beatmapSets) { + originalBeatmapSetsDetached = beatmapSets.Detach(); + CarouselRoot newRoot = new CarouselRoot(this); - newRoot.AddItems(beatmapSets.Select(s => createCarouselSet(s.Detach())).OfType()); + if (beatmapsSplitOut) + { + var carouselBeatmapSets = originalBeatmapSetsDetached.SelectMany(s => s.Beatmaps).Select(b => + { + var set = new BeatmapSetInfo(new[] { b }); + return createCarouselSet(set); + }).OfType(); + + newRoot.AddItems(carouselBeatmapSets); + } + else + { + var carouselBeatmapSets = originalBeatmapSetsDetached.Select(createCarouselSet).OfType(); + newRoot.AddItems(carouselBeatmapSets); + } root = newRoot; - if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) + if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; Scroll.Clear(false); @@ -330,8 +348,8 @@ namespace osu.Game.Screens.Select // Only require to action here if the beatmap is missing. // This avoids processing these events unnecessarily when new beatmaps are imported, for example. - if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSet) - && existingSet.BeatmapSet.Beatmaps.All(b => b.ID != beatmapInfo.ID)) + if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSets) + && existingSets.SelectMany(s => s.Beatmaps).All(b => b.BeatmapInfo.ID != beatmapInfo.ID)) { UpdateBeatmapSet(beatmapSet.Detach()); } @@ -345,15 +363,18 @@ namespace osu.Game.Screens.Select private void removeBeatmapSet(Guid beatmapSetID) => Schedule(() => { - if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSet)) + if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSets)) return; - foreach (var beatmap in existingSet.Beatmaps) - randomSelectedBeatmaps.Remove(beatmap); + foreach (var set in existingSets) + { + foreach (var beatmap in set.Beatmaps) + randomSelectedBeatmaps.Remove(beatmap); + previouslyVisitedRandomSets.Remove(set); - previouslyVisitedRandomSets.Remove(existingSet); + root.RemoveItem(set); + } - root.RemoveItem(existingSet); itemsCache.Invalidate(); if (!Scroll.UserScrolling) @@ -371,13 +392,16 @@ namespace osu.Game.Screens.Select previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; var newSet = createCarouselSet(beatmapSet); - var removedSet = root.RemoveChild(beatmapSet.ID); + var removedSets = root.RemoveChild(beatmapSet.ID); - // If we don't remove this here, it may remain in a hidden state until scrolled off screen. - // Doesn't really affect anything during actual user interaction, but makes testing annoying. - var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet); - if (removedDrawable != null) - expirePanelImmediately(removedDrawable); + foreach (var removedSet in removedSets) + { + // If we don't remove this here, it may remain in a hidden state until scrolled off screen. + // Doesn't really affect anything during actual user interaction, but makes testing annoying. + var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet); + if (removedDrawable != null) + expirePanelImmediately(removedDrawable); + } if (newSet != null) { @@ -632,6 +656,8 @@ namespace osu.Game.Screens.Select applyActiveCriteria(debounce); } + private bool beatmapsSplitOut; + private void applyActiveCriteria(bool debounce, bool alwaysResetScrollPosition = true) { PendingFilter?.Cancel(); @@ -652,6 +678,13 @@ namespace osu.Game.Screens.Select { PendingFilter = null; + if (activeCriteria.SplitOutDifficulties != beatmapsSplitOut) + { + beatmapsSplitOut = activeCriteria.SplitOutDifficulties; + loadBeatmapSets(originalBeatmapSetsDetached); + return; + } + root.Filter(activeCriteria); itemsCache.Invalidate(); @@ -1055,7 +1088,7 @@ namespace osu.Game.Screens.Select // May only be null during construction (State.Value set causes PerformSelection to be triggered). private readonly BeatmapCarousel? carousel; - public readonly Dictionary BeatmapSetsByID = new Dictionary(); + public readonly Dictionary> BeatmapSetsByID = new Dictionary>(); public CarouselRoot(BeatmapCarousel carousel) { @@ -1069,20 +1102,25 @@ namespace osu.Game.Screens.Select public override void AddItem(CarouselItem i) { CarouselBeatmapSet set = (CarouselBeatmapSet)i; - BeatmapSetsByID.Add(set.BeatmapSet.ID, set); + if (BeatmapSetsByID.TryGetValue(set.BeatmapSet.ID, out var sets)) + sets.Add(set); + else + BeatmapSetsByID.Add(set.BeatmapSet.ID, new List { set }); base.AddItem(i); } - public CarouselBeatmapSet? RemoveChild(Guid beatmapSetID) + public IEnumerable RemoveChild(Guid beatmapSetID) { - if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) + if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSets)) { - RemoveItem(carouselBeatmapSet); - return carouselBeatmapSet; + foreach (var set in carouselBeatmapSets) + RemoveItem(set); + + return carouselBeatmapSets; } - return null; + return Enumerable.Empty(); } public override void RemoveItem(CarouselItem i) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index ab4f85fc92..a2ae114126 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -19,6 +19,11 @@ namespace osu.Game.Screens.Select public GroupMode Group; public SortMode Sort; + /// + /// Whether the display of beatmap sets should be split apart per-difficulty for the current criteria. + /// + public bool SplitOutDifficulties => Sort == SortMode.Difficulty; + public BeatmapSetInfo? SelectedBeatmapSet; public OptionalRange StarDifficulty; From 2b1c6ae612cb4e6cecbd6736748da942fa724e39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 18:38:23 +0900 Subject: [PATCH 09/52] Ensure ID is maintained in temporary `BeatmapSetInfo`s --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d1a9b4176b..5157e37a31 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -137,8 +137,10 @@ namespace osu.Game.Screens.Select { var carouselBeatmapSets = originalBeatmapSetsDetached.SelectMany(s => s.Beatmaps).Select(b => { - var set = new BeatmapSetInfo(new[] { b }); - return createCarouselSet(set); + return createCarouselSet(new BeatmapSetInfo(new[] { b }) + { + ID = b.BeatmapSet!.ID, + }); }).OfType(); newRoot.AddItems(carouselBeatmapSets); From ecbf0f138e2c7b3cf5716020a1aad4f8a834dfd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 18:38:43 +0900 Subject: [PATCH 10/52] Fix incorrect handling when new beatmaps arrive --- osu.Game/Screens/Select/BeatmapCarousel.cs | 35 ++++++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5157e37a31..2227eb801a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -393,7 +393,6 @@ namespace osu.Game.Screens.Select if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; - var newSet = createCarouselSet(beatmapSet); var removedSets = root.RemoveChild(beatmapSet.ID); foreach (var removedSet in removedSets) @@ -405,13 +404,37 @@ namespace osu.Game.Screens.Select expirePanelImmediately(removedDrawable); } - if (newSet != null) + if (beatmapsSplitOut) { - root.AddItem(newSet); + foreach (var beatmap in beatmapSet.Beatmaps) + { + var newSet = createCarouselSet(new BeatmapSetInfo(new[] { beatmap }) + { + ID = beatmapSet.ID + }); - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) - select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); + if (newSet != null) + { + root.AddItem(newSet); + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); + } + } + } + else + { + var newSet = createCarouselSet(beatmapSet); + + if (newSet != null) + { + root.AddItem(newSet); + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); + } } itemsCache.Invalidate(); From 018be4c20f408ee98bb00a3897e2658b4f016596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Aug 2023 18:40:34 +0900 Subject: [PATCH 11/52] Fix selection not being retained when switching between split mode --- osu.Game/Screens/Select/BeatmapCarousel.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2227eb801a..c5e46a00b6 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -131,6 +131,12 @@ namespace osu.Game.Screens.Select { originalBeatmapSetsDetached = beatmapSets.Detach(); + if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) + selectedBeatmapSet = null; + + var selectedSetBefore = selectedBeatmapSet; + var selectedBetmapBefore = selectedBeatmap; + CarouselRoot newRoot = new CarouselRoot(this); if (beatmapsSplitOut) @@ -148,14 +154,12 @@ namespace osu.Game.Screens.Select else { var carouselBeatmapSets = originalBeatmapSetsDetached.Select(createCarouselSet).OfType(); + newRoot.AddItems(carouselBeatmapSets); } root = newRoot; - if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) - selectedBeatmapSet = null; - Scroll.Clear(false); itemsCache.Invalidate(); ScrollToSelected(); @@ -164,6 +168,15 @@ namespace osu.Game.Screens.Select if (loadedTestBeatmaps) signalBeatmapsLoaded(); + + // Restore selection + if (selectedBetmapBefore != null && selectedSetBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedSetBefore.BeatmapSet.ID, out var newSelectionCandidates)) + { + CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBetmapBefore.BeatmapInfo.ID); + + if (found != null) + found.State.Value = CarouselItemState.Selected; + } } private readonly List visibleItems = new List(); From 5555f73e97f22f9c6f0425bc4c9459bef88f3be5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 19:38:18 +0900 Subject: [PATCH 12/52] Update test to match new behaviour --- .../SongSelect/TestSceneBeatmapCarousel.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 61f95dc628..b0aff8b4db 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -758,7 +758,7 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - public void TestSortingWithFiltered() + public void TestSortingWithDifficultyFiltered() { List sets = new List(); @@ -777,13 +777,32 @@ namespace osu.Game.Tests.Visual.SongSelect loadBeatmaps(sets); + AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + + checkVisibleItemCount(false, 9); + checkVisibleItemCount(true, 1); + AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false)); - AddAssert("Check first set at end", () => carousel.BeatmapSets.First().Equals(sets.Last())); - AddAssert("Check last set at start", () => carousel.BeatmapSets.Last().Equals(sets.First())); + checkVisibleItemCount(false, 3); + checkVisibleItemCount(true, 1); + + AddUntilStep("Check all visible sets have one normal", () => + { + return carousel.Items.OfType() + .Where(p => p.IsPresent) + .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == 3; + }); AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false)); - AddAssert("Check first set at start", () => carousel.BeatmapSets.First().Equals(sets.First())); - AddAssert("Check last set at end", () => carousel.BeatmapSets.Last().Equals(sets.Last())); + checkVisibleItemCount(false, 3); + checkVisibleItemCount(true, 1); + + AddUntilStep("Check all visible sets have one insane", () => + { + return carousel.Items.OfType() + .Where(p => p.IsPresent) + .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Insane", StringComparison.Ordinal)) == 3; + }); } [Test] From a64381f8553dd49b9c5c5142aa2934c333b101a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 19:43:08 +0900 Subject: [PATCH 13/52] Add test coverage of add/remove when difficulties are split out --- .../SongSelect/TestSceneBeatmapCarousel.cs | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index b0aff8b4db..5af6d862b2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -39,6 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelect private BeatmapInfo currentSelection => carousel.SelectedBeatmapInfo; private const int set_count = 5; + private const int diff_count = 3; [BackgroundDependencyLoader] private void load(RulesetStore rulesets) @@ -501,6 +502,36 @@ namespace osu.Game.Tests.Visual.SongSelect waitForSelection(set_count); } + [Test] + public void TestAddRemoveDifficultySort() + { + loadBeatmaps(); + + AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + + checkVisibleItemCount(false, set_count * diff_count); + + var firstAdded = TestResources.CreateTestBeatmapSetInfo(diff_count); + var secondAdded = TestResources.CreateTestBeatmapSetInfo(diff_count); + + AddStep("Add new set", () => carousel.UpdateBeatmapSet(firstAdded)); + AddStep("Add new set", () => carousel.UpdateBeatmapSet(secondAdded)); + + checkVisibleItemCount(false, (set_count + 2) * diff_count); + + AddStep("Remove set", () => carousel.RemoveBeatmapSet(firstAdded)); + + checkVisibleItemCount(false, (set_count + 1) * diff_count); + + setSelected(set_count + 1, 1); + + AddStep("Remove set", () => carousel.RemoveBeatmapSet(secondAdded)); + + checkVisibleItemCount(false, (set_count) * diff_count); + + waitForSelection(set_count); + } + [Test] public void TestSelectionEnteringFromEmptyRuleset() { @@ -662,7 +693,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(3); + var set = TestResources.CreateTestBeatmapSetInfo(diff_count); // only need to set the first as they are a shared reference. var beatmap = set.Beatmaps.First(); @@ -709,7 +740,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(3); + var set = TestResources.CreateTestBeatmapSetInfo(diff_count); // only need to set the first as they are a shared reference. var beatmap = set.Beatmaps.First(); @@ -768,7 +799,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < 3; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(3); + var set = TestResources.CreateTestBeatmapSetInfo(diff_count); set.Beatmaps[0].StarRating = 3 - i; set.Beatmaps[2].StarRating = 6 + i; sets.Add(set); @@ -857,7 +888,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("create hidden set", () => { - hidingSet = TestResources.CreateTestBeatmapSetInfo(3); + hidingSet = TestResources.CreateTestBeatmapSetInfo(diff_count); hidingSet.Beatmaps[1].Hidden = true; hiddenList.Clear(); @@ -904,7 +935,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("add mixed ruleset beatmapset", () => { - testMixed = TestResources.CreateTestBeatmapSetInfo(3); + testMixed = TestResources.CreateTestBeatmapSetInfo(diff_count); for (int i = 0; i <= 2; i++) { @@ -926,7 +957,7 @@ namespace osu.Game.Tests.Visual.SongSelect BeatmapSetInfo testSingle = null; AddStep("add single ruleset beatmapset", () => { - testSingle = TestResources.CreateTestBeatmapSetInfo(3); + testSingle = TestResources.CreateTestBeatmapSetInfo(diff_count); testSingle.Beatmaps.ForEach(b => { b.Ruleset = rulesets.AvailableRulesets.ElementAt(1); @@ -949,7 +980,7 @@ namespace osu.Game.Tests.Visual.SongSelect manySets.Clear(); for (int i = 1; i <= 50; i++) - manySets.Add(TestResources.CreateTestBeatmapSetInfo(3)); + manySets.Add(TestResources.CreateTestBeatmapSetInfo(diff_count)); }); loadBeatmaps(manySets); @@ -1113,7 +1144,7 @@ namespace osu.Game.Tests.Visual.SongSelect { beatmapSets.Add(randomDifficulties ? TestResources.CreateTestBeatmapSetInfo() - : TestResources.CreateTestBeatmapSetInfo(3)); + : TestResources.CreateTestBeatmapSetInfo(diff_count)); } } From 5eac604f8b2cf59a5346956d99a7fea0fdda0cd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Aug 2023 19:44:39 +0900 Subject: [PATCH 14/52] Add coverage of selection retention when difficulties are split out --- .../SongSelect/TestSceneBeatmapCarousel.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 5af6d862b2..daa8c9c4c2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1005,6 +1005,43 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1); } + [Test] + public void TestCarouselRemembersSelectionDifficultySort() + { + List manySets = new List(); + + AddStep("Populuate beatmap sets", () => + { + manySets.Clear(); + + for (int i = 1; i <= 50; i++) + manySets.Add(TestResources.CreateTestBeatmapSetInfo(diff_count)); + }); + + loadBeatmaps(manySets); + + AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); + + advanceSelection(direction: 1, diff: false); + + for (int i = 0; i < 5; i++) + { + AddStep("Toggle non-matching filter", () => + { + carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false); + }); + + AddStep("Restore no filter", () => + { + carousel.Filter(new FilterCriteria(), false); + eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID); + }); + } + + // always returns to same selection as long as it's available. + AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1); + } + [Test] public void TestFilteringByUserStarDifficulty() { From 4881130caeccc728dbb789665fd161da1cfb0b53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Aug 2023 03:32:12 +0900 Subject: [PATCH 15/52] Limit set/diff count in test to better fit on screen --- .../SongSelect/TestSceneBeatmapCarousel.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index daa8c9c4c2..d68fa6100c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -791,17 +791,20 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestSortingWithDifficultyFiltered() { + const int local_diff_count = 3; + const int local_set_count = 2; + List sets = new List(); AddStep("Populuate beatmap sets", () => { sets.Clear(); - for (int i = 0; i < 3; i++) + for (int i = 0; i < local_set_count; i++) { - var set = TestResources.CreateTestBeatmapSetInfo(diff_count); + var set = TestResources.CreateTestBeatmapSetInfo(local_diff_count); set.Beatmaps[0].StarRating = 3 - i; - set.Beatmaps[2].StarRating = 6 + i; + set.Beatmaps[1].StarRating = 6 + i; sets.Add(set); } }); @@ -810,29 +813,29 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); - checkVisibleItemCount(false, 9); + checkVisibleItemCount(false, local_set_count * local_diff_count); checkVisibleItemCount(true, 1); AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false)); - checkVisibleItemCount(false, 3); + checkVisibleItemCount(false, local_set_count); checkVisibleItemCount(true, 1); AddUntilStep("Check all visible sets have one normal", () => { return carousel.Items.OfType() .Where(p => p.IsPresent) - .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == 3; + .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == local_set_count; }); AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false)); - checkVisibleItemCount(false, 3); + checkVisibleItemCount(false, local_set_count); checkVisibleItemCount(true, 1); AddUntilStep("Check all visible sets have one insane", () => { return carousel.Items.OfType() .Where(p => p.IsPresent) - .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Insane", StringComparison.Ordinal)) == 3; + .Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Insane", StringComparison.Ordinal)) == local_set_count; }); } From ba70d48d2cc0013fbc42338e0867cbe1e6ea8c82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Aug 2023 13:31:33 +0900 Subject: [PATCH 16/52] Fix one more test probably going off-screen --- .../SongSelect/TestSceneBeatmapCarousel.cs | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index d68fa6100c..8a38e2a84b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestScrollPositionMaintainedOnAdd() { - loadBeatmaps(count: 1, randomDifficulties: false); + loadBeatmaps(setCount: 1); for (int i = 0; i < 10; i++) { @@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestDeletion() { - loadBeatmaps(count: 5, randomDifficulties: true); + loadBeatmaps(setCount: 5, randomDifficulties: true); AddStep("remove first set", () => carousel.RemoveBeatmapSet(carousel.Items.Select(item => item.Item).OfType().First().BeatmapSet)); AddUntilStep("4 beatmap sets visible", () => this.ChildrenOfType().Count(set => set.Alpha > 0) == 4); @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestScrollPositionMaintainedOnDelete() { - loadBeatmaps(count: 50, randomDifficulties: false); + loadBeatmaps(setCount: 50); for (int i = 0; i < 10; i++) { @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestManyPanels() { - loadBeatmaps(count: 5000, randomDifficulties: true); + loadBeatmaps(setCount: 5000, randomDifficulties: true); } [Test] @@ -505,31 +505,34 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestAddRemoveDifficultySort() { - loadBeatmaps(); + const int local_set_count = 1; + const int local_diff_count = 1; + + loadBeatmaps(setCount: local_set_count, diffCount: local_diff_count); AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false)); - checkVisibleItemCount(false, set_count * diff_count); + checkVisibleItemCount(false, local_set_count * local_diff_count); - var firstAdded = TestResources.CreateTestBeatmapSetInfo(diff_count); - var secondAdded = TestResources.CreateTestBeatmapSetInfo(diff_count); + var firstAdded = TestResources.CreateTestBeatmapSetInfo(local_diff_count); + var secondAdded = TestResources.CreateTestBeatmapSetInfo(local_diff_count); AddStep("Add new set", () => carousel.UpdateBeatmapSet(firstAdded)); AddStep("Add new set", () => carousel.UpdateBeatmapSet(secondAdded)); - checkVisibleItemCount(false, (set_count + 2) * diff_count); + checkVisibleItemCount(false, (local_set_count + 2) * local_diff_count); AddStep("Remove set", () => carousel.RemoveBeatmapSet(firstAdded)); - checkVisibleItemCount(false, (set_count + 1) * diff_count); + checkVisibleItemCount(false, (local_set_count + 1) * local_diff_count); - setSelected(set_count + 1, 1); + setSelected(local_set_count + 1, 1); AddStep("Remove set", () => carousel.RemoveBeatmapSet(secondAdded)); - checkVisibleItemCount(false, (set_count) * diff_count); + checkVisibleItemCount(false, (local_set_count) * local_diff_count); - waitForSelection(set_count); + waitForSelection(local_set_count); } [Test] @@ -1171,8 +1174,8 @@ namespace osu.Game.Tests.Visual.SongSelect } } - private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, int? count = null, - bool randomDifficulties = false) + private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, + int? setCount = null, int? diffCount = null, bool randomDifficulties = false) { bool changed = false; @@ -1180,11 +1183,11 @@ namespace osu.Game.Tests.Visual.SongSelect { beatmapSets = new List(); - for (int i = 1; i <= (count ?? set_count); i++) + for (int i = 1; i <= (setCount ?? set_count); i++) { beatmapSets.Add(randomDifficulties ? TestResources.CreateTestBeatmapSetInfo() - : TestResources.CreateTestBeatmapSetInfo(diff_count)); + : TestResources.CreateTestBeatmapSetInfo(diffCount ?? diff_count)); } } From 2e27a476bb3c4b90d08384cfda3da7e60774e278 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 24 Aug 2023 18:04:47 +0900 Subject: [PATCH 17/52] Re-enable sample playback for `PasswordEntryPopover` and remove `sampleJoin` playback instead --- osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs | 11 +++-------- .../Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 5 ----- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 2aa42d0d50..551468beb9 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -31,8 +31,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 // required due to LoadAsyncComplete() calling PopOut() during load - similar workaround to `OsuDropdownMenu` private bool wasOpened; - protected virtual bool PlayPopInOutSamples => true; - public OsuPopover(bool withPadding = true) { Content.Padding = withPadding ? new MarginPadding(20) : new MarginPadding(); @@ -64,11 +62,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 this.ScaleTo(1, scale_duration, Easing.OutElasticHalf); this.FadeIn(fade_duration, Easing.OutQuint); - if (PlayPopInOutSamples) - { - samplePopIn?.Play(); - wasOpened = true; - } + samplePopIn?.Play(); + wasOpened = true; } protected override void PopOut() @@ -76,7 +71,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 this.ScaleTo(0.7f, scale_duration, Easing.OutQuint); this.FadeOut(fade_duration, Easing.OutQuint); - if (wasOpened && PlayPopInOutSamples) + if (wasOpened) samplePopOut?.Play(); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index a45583a2ec..5cf2f91ff4 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -170,7 +170,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge if (Room.HasPassword.Value) { - sampleJoin?.Play(); this.ShowPopover(); return true; } @@ -191,10 +190,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge protected override bool BlockNonPositionalInput => true; - // When a room is clicked, it already plays a click sound, which clashes pretty badly with the pop in sound. - // Dunno about this one. I'd probably remove the click sound from the panel in cases they are password protected and play these pop in / out sounds. - protected override bool PlayPopInOutSamples => false; - public PasswordEntryPopover(Room room) { this.room = room; From 7ef5a71e91a56a3e8340393376a97973ceb3e266 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 24 Aug 2023 18:13:23 +0900 Subject: [PATCH 18/52] Move PopIn/PopOut sample playback from `WaveOverlayContainer` to `WaveContainer` (so Multiplayer/Lounge plays the samples) --- osu.Game/Graphics/Containers/WaveContainer.cs | 22 +++++++++++++++++++ osu.Game/Overlays/WaveOverlayContainer.cs | 5 +++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 9fd3d103c9..0b1d44ccba 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -2,6 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -32,6 +35,13 @@ namespace osu.Game.Graphics.Containers protected override bool StartHidden => true; + private Sample? samplePopIn; + private Sample? samplePopOut; + protected virtual string PopInSampleName => "UI/wave-pop-in"; + protected virtual string PopOutSampleName => "UI/overlay-big-pop-out"; + + private bool wasShown = false; + public Color4 FirstWaveColour { get => firstWave.Colour; @@ -56,6 +66,13 @@ namespace osu.Game.Graphics.Containers set => fourthWave.Colour = value; } + [BackgroundDependencyLoader(true)] + private void load(AudioManager audio) + { + samplePopIn = audio.Samples.Get(PopInSampleName); + samplePopOut = audio.Samples.Get(PopOutSampleName); + } + public WaveContainer() { Masking = true; @@ -110,6 +127,8 @@ namespace osu.Game.Graphics.Containers w.Show(); contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint); + samplePopIn?.Play(); + wasShown = true; } protected override void PopOut() @@ -118,6 +137,9 @@ namespace osu.Game.Graphics.Containers w.Hide(); contentContainer.MoveToY(2, DISAPPEAR_DURATION, Easing.In); + + if (wasShown) + samplePopOut?.Play(); } protected override void UpdateAfterChildren() diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 848d9e60b7..b0ddef5c2b 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -18,8 +18,9 @@ namespace osu.Game.Overlays protected override bool StartHidden => true; - protected override string PopInSampleName => "UI/wave-pop-in"; - protected override string PopOutSampleName => "UI/overlay-big-pop-out"; + // `WaveContainer` plays PopIn/PopOut samples, so we disable the overlay-level one as to not double-up sample playback. + protected override string PopInSampleName => ""; + protected override string PopOutSampleName => ""; public const float HORIZONTAL_PADDING = 50; From f4415a5bab9aec67ea77af89c794921dae6a8c4e Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 24 Aug 2023 18:20:36 +0900 Subject: [PATCH 19/52] Add more detail to comment --- osu.Game/Graphics/Containers/WaveContainer.cs | 3 ++- osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 0b1d44ccba..09d1f7e9a4 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -40,7 +40,8 @@ namespace osu.Game.Graphics.Containers protected virtual string PopInSampleName => "UI/wave-pop-in"; protected virtual string PopOutSampleName => "UI/overlay-big-pop-out"; - private bool wasShown = false; + // required due to LoadAsyncComplete() in `VisibilityContainer` calling PopOut() during load - similar workaround to `OsuDropdownMenu` + private bool wasShown; public Color4 FirstWaveColour { diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 551468beb9..9b4689958c 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -28,7 +28,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected virtual string PopInSampleName => "UI/overlay-pop-in"; protected virtual string PopOutSampleName => "UI/overlay-pop-out"; - // required due to LoadAsyncComplete() calling PopOut() during load - similar workaround to `OsuDropdownMenu` + // required due to LoadAsyncComplete() in `VisibilityContainer` calling PopOut() during load - similar workaround to `OsuDropdownMenu` private bool wasOpened; public OsuPopover(bool withPadding = true) From b471ab07a62dc225275154efdfd15c46646f0104 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Aug 2023 18:30:59 +0900 Subject: [PATCH 20/52] Fix typo in test step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 8a38e2a84b..110466f6d5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1016,7 +1016,7 @@ namespace osu.Game.Tests.Visual.SongSelect { List manySets = new List(); - AddStep("Populuate beatmap sets", () => + AddStep("Populate beatmap sets", () => { manySets.Clear(); From 9e94f3809103b6079a158e19050f9bc15829235a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Aug 2023 18:33:15 +0900 Subject: [PATCH 21/52] Fix typo in local variable --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c5e46a00b6..91d1ced9d9 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Select selectedBeatmapSet = null; var selectedSetBefore = selectedBeatmapSet; - var selectedBetmapBefore = selectedBeatmap; + var selectedBeatmapBefore = selectedBeatmap; CarouselRoot newRoot = new CarouselRoot(this); @@ -170,9 +170,9 @@ namespace osu.Game.Screens.Select signalBeatmapsLoaded(); // Restore selection - if (selectedBetmapBefore != null && selectedSetBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedSetBefore.BeatmapSet.ID, out var newSelectionCandidates)) + if (selectedBeatmapBefore != null && selectedSetBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedSetBefore.BeatmapSet.ID, out var newSelectionCandidates)) { - CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBetmapBefore.BeatmapInfo.ID); + CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBeatmapBefore.BeatmapInfo.ID); if (found != null) found.State.Value = CarouselItemState.Selected; From 89eeff515b42a9724ffd2e552f9e7d462f848d5a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Aug 2023 00:52:54 +0900 Subject: [PATCH 22/52] Reduce complexity of selection restore --- osu.Game/Screens/Select/BeatmapCarousel.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 91d1ced9d9..d52b5592f8 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -134,8 +134,7 @@ namespace osu.Game.Screens.Select if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; - var selectedSetBefore = selectedBeatmapSet; - var selectedBeatmapBefore = selectedBeatmap; + var selectedBeatmapBefore = selectedBeatmap?.BeatmapInfo; CarouselRoot newRoot = new CarouselRoot(this); @@ -170,9 +169,9 @@ namespace osu.Game.Screens.Select signalBeatmapsLoaded(); // Restore selection - if (selectedBeatmapBefore != null && selectedSetBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedSetBefore.BeatmapSet.ID, out var newSelectionCandidates)) + if (selectedBeatmapBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedBeatmapBefore.BeatmapSet!.ID, out var newSelectionCandidates)) { - CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBeatmapBefore.BeatmapInfo.ID); + CarouselBeatmap? found = newSelectionCandidates.SelectMany(s => s.Beatmaps).SingleOrDefault(b => b.BeatmapInfo.ID == selectedBeatmapBefore.ID); if (found != null) found.State.Value = CarouselItemState.Selected; From 35cdd6d8661b2b4df2735e4128c3334fd014670f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Aug 2023 01:07:07 +0900 Subject: [PATCH 23/52] Use `string.Empty` --- osu.Game/Overlays/WaveOverlayContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index b0ddef5c2b..0295ff467a 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -19,8 +19,8 @@ namespace osu.Game.Overlays protected override bool StartHidden => true; // `WaveContainer` plays PopIn/PopOut samples, so we disable the overlay-level one as to not double-up sample playback. - protected override string PopInSampleName => ""; - protected override string PopOutSampleName => ""; + protected override string PopInSampleName => string.Empty; + protected override string PopOutSampleName => string.Empty; public const float HORIZONTAL_PADDING = 50; From bf0f4fddad3c2c5e6a54b41333fea68daef82b2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Aug 2023 01:08:22 +0900 Subject: [PATCH 24/52] Localise non-overridden samples --- osu.Game/Graphics/Containers/WaveContainer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 09d1f7e9a4..e84cb276a4 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -37,8 +37,6 @@ namespace osu.Game.Graphics.Containers private Sample? samplePopIn; private Sample? samplePopOut; - protected virtual string PopInSampleName => "UI/wave-pop-in"; - protected virtual string PopOutSampleName => "UI/overlay-big-pop-out"; // required due to LoadAsyncComplete() in `VisibilityContainer` calling PopOut() during load - similar workaround to `OsuDropdownMenu` private bool wasShown; @@ -70,8 +68,8 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader(true)] private void load(AudioManager audio) { - samplePopIn = audio.Samples.Get(PopInSampleName); - samplePopOut = audio.Samples.Get(PopOutSampleName); + samplePopIn = audio.Samples.Get("UI/wave-pop-in"); + samplePopOut = audio.Samples.Get("UI/overlay-big-pop-out"); } public WaveContainer() From 4fd165c0a7dc6b9c49cb0e675de6084ed47fbdc0 Mon Sep 17 00:00:00 2001 From: Dreamurrrr <21233238+Dreamurrrr@users.noreply.github.com> Date: Thu, 24 Aug 2023 13:28:17 -0500 Subject: [PATCH 25/52] Update README.md to include VS Code plugin prerequisites --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf7ce35791..792e2d646a 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Please make sure you have the following prerequisites: - A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed. -When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). +When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C#](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) plugin installed. ### Downloading the source code From 84f4fab9cfad4a122a271977ed7902f9e504904b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Aug 2023 18:09:51 +0900 Subject: [PATCH 26/52] Adjust test to actually test diff splitting --- .../SongSelect/TestSceneBeatmapCarousel.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 110466f6d5..040b341584 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -505,8 +505,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestAddRemoveDifficultySort() { - const int local_set_count = 1; - const int local_diff_count = 1; + const int local_set_count = 2; + const int local_diff_count = 2; loadBeatmaps(setCount: local_set_count, diffCount: local_diff_count); @@ -515,23 +515,17 @@ namespace osu.Game.Tests.Visual.SongSelect checkVisibleItemCount(false, local_set_count * local_diff_count); var firstAdded = TestResources.CreateTestBeatmapSetInfo(local_diff_count); - var secondAdded = TestResources.CreateTestBeatmapSetInfo(local_diff_count); AddStep("Add new set", () => carousel.UpdateBeatmapSet(firstAdded)); - AddStep("Add new set", () => carousel.UpdateBeatmapSet(secondAdded)); - - checkVisibleItemCount(false, (local_set_count + 2) * local_diff_count); - - AddStep("Remove set", () => carousel.RemoveBeatmapSet(firstAdded)); checkVisibleItemCount(false, (local_set_count + 1) * local_diff_count); - setSelected(local_set_count + 1, 1); - - AddStep("Remove set", () => carousel.RemoveBeatmapSet(secondAdded)); + AddStep("Remove set", () => carousel.RemoveBeatmapSet(firstAdded)); checkVisibleItemCount(false, (local_set_count) * local_diff_count); + setSelected(local_set_count, 1); + waitForSelection(local_set_count); } From 10b14501380320939f43fe715b033970b8af4a2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Aug 2023 18:10:54 +0900 Subject: [PATCH 27/52] Rename remove method to better explain return type being `IEnumerable` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d52b5592f8..a8da5dc742 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -405,7 +405,7 @@ namespace osu.Game.Screens.Select if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; - var removedSets = root.RemoveChild(beatmapSet.ID); + var removedSets = root.RemoveItemsByID(beatmapSet.ID); foreach (var removedSet in removedSets) { @@ -1147,7 +1147,7 @@ namespace osu.Game.Screens.Select base.AddItem(i); } - public IEnumerable RemoveChild(Guid beatmapSetID) + public IEnumerable RemoveItemsByID(Guid beatmapSetID) { if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSets)) { From 3a6920c3067f90c53578d021f3ecf7e89d368c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 09:51:37 +0200 Subject: [PATCH 28/52] Add failing test coverage of beatmap update flow w/ split diffs --- .../TestSceneUpdateBeatmapSetButton.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index 11d55bc0bd..585d93d3af 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -15,6 +15,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; +using osu.Game.Screens.Select.Filter; using osu.Game.Tests.Online; using osu.Game.Tests.Resources; using osuTK.Input; @@ -192,6 +193,57 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); } + [Test] + public void TestSplitDisplay() + { + ArchiveDownloadRequest? downloadRequest = null; + + AddStep("set difficulty sort mode", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty })); + AddStep("update online hash", () => + { + testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + }); + + AddUntilStep("multiple \"sets\" visible", () => carousel.ChildrenOfType().Count(), () => Is.GreaterThan(1)); + AddUntilStep("update button visible", getUpdateButton, () => Is.Not.Null); + + AddStep("click button", () => getUpdateButton()?.TriggerClick()); + + AddUntilStep("wait for download started", () => + { + downloadRequest = beatmapDownloader.GetExistingDownload(testBeatmapSetInfo); + return downloadRequest != null; + }); + + AddUntilStep("wait for button disabled", () => getUpdateButton()?.Enabled.Value == false); + + AddUntilStep("progress download to completion", () => + { + if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) + { + testRequest.SetProgress(testRequest.Progress + 0.1f); + + if (testRequest.Progress >= 1) + { + testRequest.TriggerSuccess(); + + // usually this would be done by the import process. + testBeatmapSetInfo.Beatmaps.First().MD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + // usually this would be done by a realm subscription. + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + return true; + } + } + + return false; + }); + } + private BeatmapCarousel createCarousel() { return carousel = new BeatmapCarousel From 6251803868588f188ca7b78f74ad4c9e18d4b10b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 10:02:05 +0200 Subject: [PATCH 29/52] Add failing test coverage of selection not being retained on song select --- .../TestSceneBeatmapEditorNavigation.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 54ee1659e1..d0fa5fc737 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; @@ -16,6 +17,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Filter; using osu.Game.Tests.Resources; using osuTK.Input; @@ -203,6 +205,33 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for music stopped", () => !Game.MusicController.IsPlaying); } + [TestCase(SortMode.Title)] + [TestCase(SortMode.Difficulty)] + public void TestSelectionRetainedOnExit(SortMode sortMode) + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep($"set sort mode to {sortMode}", () => Game.LocalConfig.SetValue(OsuSetting.SongSelectSortingMode, sortMode)); + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + + AddStep("exit editor", () => InputManager.Key(Key.Escape)); + AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor); + + AddUntilStep("selection retained on song select", + () => Game.Beatmap.Value.BeatmapInfo.ID, + () => Is.EqualTo(beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0).ID)); + } + private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType().Single(); private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen; From 0af6cc1394f7fe521bdf94773b423b5ff61c2bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 10:02:30 +0200 Subject: [PATCH 30/52] Fix online ID not being propagated in split difficulty mode Would result in failures to re-download the beatmap in update flows, for instance. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a8da5dc742..0354e72803 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -145,6 +145,7 @@ namespace osu.Game.Screens.Select return createCarouselSet(new BeatmapSetInfo(new[] { b }) { ID = b.BeatmapSet!.ID, + OnlineID = b.BeatmapSet!.OnlineID }); }).OfType(); @@ -422,7 +423,8 @@ namespace osu.Game.Screens.Select { var newSet = createCarouselSet(new BeatmapSetInfo(new[] { beatmap }) { - ID = beatmapSet.ID + ID = beatmapSet.ID, + OnlineID = beatmapSet.OnlineID }); if (newSet != null) From 80ec18d117fc7ae252731bcde4e65a71be838ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 10:06:26 +0200 Subject: [PATCH 31/52] Fix incorrect selection restore code in split case The fallback to "any of the added sets" needs to be applied after they've all been added, rather than with every added one. Otherwise, in flows that expect a particular difficulty to be selected in the end (such as exiting from editor) would end up switching away from the edited beatmap. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0354e72803..861385f453 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -419,6 +419,8 @@ namespace osu.Game.Screens.Select if (beatmapsSplitOut) { + var newSets = new List(); + foreach (var beatmap in beatmapSet.Beatmaps) { var newSet = createCarouselSet(new BeatmapSetInfo(new[] { beatmap }) @@ -429,13 +431,18 @@ namespace osu.Game.Screens.Select if (newSet != null) { + newSets.Add(newSet); root.AddItem(newSet); - - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) - select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); } } + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + { + var toSelect = newSets.FirstOrDefault(s => s.Beatmaps.Any(b => b.BeatmapInfo.ID == previouslySelectedID)) + ?? newSets.FirstOrDefault(); + select(toSelect); + } } else { From b9795eb3d4e09d66c9e2950da6e8742637dd8dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 11:02:22 +0200 Subject: [PATCH 32/52] Fix changes to beatmap sets being undone on switching sort mode --- osu.Game/Screens/Select/BeatmapCarousel.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 861385f453..9ea94b2a52 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Select private CarouselBeatmapSet? selectedBeatmapSet; - private IEnumerable originalBeatmapSetsDetached = Enumerable.Empty(); + private List originalBeatmapSetsDetached = new List(); /// /// Raised when the is changed. @@ -381,6 +381,8 @@ namespace osu.Game.Screens.Select if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSets)) return; + originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSetID); + foreach (var set in existingSets) { foreach (var beatmap in set.Beatmaps) @@ -402,6 +404,9 @@ namespace osu.Game.Screens.Select { Guid? previouslySelectedID = null; + originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSet.ID); + originalBeatmapSetsDetached.Add(beatmapSet.Detach()); + // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; From ba1f6439bf0dc3d5d283c5cf32ce35d8b93acebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 12:44:57 +0200 Subject: [PATCH 33/52] Disable redundant verbatim string prefix inspections For some reason this started flaring up recently all over for me and showing inspections all over, which are _technically_ valid, but interfere with our convention of using verbatim string prefixes to denote non-localisable strings. This, as a result, led to circular inspections (addressing the r# inspection results in getting the osu-localisation-analyser one, addresssing that one results in getting the r# inspection back, etc. ad nauseam). --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index ef39c12768..c2778ca5b1 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -170,7 +170,7 @@ ERROR WARNING WARNING - HINT + DO_NOT_SHOW WARNING WARNING WARNING From 99d5ff9efbd7b7197497acd53f757345dc1666b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 13:50:52 +0200 Subject: [PATCH 34/52] Switch download requests to new API endpoint This API endpoint is intended for usage with the entire `solo_scores` machinery and ID schema, rather than the legacy `*_scores_high` ID schema. It also supports automagically falling back to downloading legacy replays if a stable-imported score is requested for download (internally this happens via `legacy_score_id` in the `data` json). This change will allow replays to be downloaded, but it will still not yield 100% correct behaviour, as there is further work to be done in that respect. The download tracker is expecting score hashes to arrive from web to verify the integrity of the incoming download, but the API does not expose such a facility right now; we will have to decide as to whether we want to add one web-side, or whether we want to disable the checking client-side. --- osu.Game/Online/API/Requests/DownloadReplayRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/DownloadReplayRequest.cs b/osu.Game/Online/API/Requests/DownloadReplayRequest.cs index 77174f0bb5..3ea57cf637 100644 --- a/osu.Game/Online/API/Requests/DownloadReplayRequest.cs +++ b/osu.Game/Online/API/Requests/DownloadReplayRequest.cs @@ -14,6 +14,6 @@ namespace osu.Game.Online.API.Requests protected override string FileExtension => ".osr"; - protected override string Target => $@"scores/{Model.Ruleset.ShortName}/{Model.OnlineID}/download"; + protected override string Target => $@"scores/{Model.OnlineID}/download"; } } From 118c86df342340da729c3931a4c58a1ed72e0f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Aug 2023 14:57:00 +0200 Subject: [PATCH 35/52] Fix `TestSceneUpdateBeatmapSetButton` using random difficulty count --- .../Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index 585d93d3af..6d97be730b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -251,7 +251,7 @@ namespace osu.Game.Tests.Visual.SongSelect RelativeSizeAxes = Axes.Both, BeatmapSets = new List { - (testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo()), + (testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(5)), } }; } From d07530b24102faaf6929ce12221bc5903d126095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 29 Aug 2023 11:48:47 +0200 Subject: [PATCH 36/52] Mark "Score V2" mod as not user-playable The mod generally will only be present on scores imported from stable. As such, it's probably ok to mark it as such. The primary reason for this change is to address #24436 (Score V2 being visible on beatmap overlay leaderboard mod selector). There is one possibly-unintended consequence of this change, namely that the results screen uses `UserPlayable` to determine as to whether animations should be played back, with the intention of turning off the animation playback for autoplay scores specifically. Therefore, turning off this flag will mean that the results screen animations will not play out for Score V2 scores - but I tend to consider this as either largely unimportant, or something that should be fixed in some other way (possibly by checking against the autoplay mod directly). Other usages of `UserPlayable` are either innocuous, or straight-up good safeties going forward in the context of Score V2 (guards against selection in mod select overlays, against score submission with the mod). --- osu.Game/Rulesets/Mods/ModScoreV2.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/ModScoreV2.cs b/osu.Game/Rulesets/Mods/ModScoreV2.cs index 6d56b2d86f..df83d96769 100644 --- a/osu.Game/Rulesets/Mods/ModScoreV2.cs +++ b/osu.Game/Rulesets/Mods/ModScoreV2.cs @@ -16,5 +16,6 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.System; public override LocalisableString Description => "Score set on earlier osu! versions with the V2 scoring algorithm active."; public override double ScoreMultiplier => 1; + public override bool UserPlayable => false; } } From b658b0e3462170adcb3a57901de17ee514a8d9c1 Mon Sep 17 00:00:00 2001 From: Nabile Rahmani Date: Tue, 29 Aug 2023 23:28:50 +0200 Subject: [PATCH 37/52] Fix and use score user's IsBot property in results screen animation While a mod-created replay did flag itself as performed by a bot, the extension method converting it into a Score did not copy all the generated properties. As noted, it might be preferable for ModCreatedUser to inherit APIUser and forward it as-is to the Score instance. Related to PR #24675 --- osu.Game/Rulesets/Mods/ModExtensions.cs | 4 +++- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModExtensions.cs b/osu.Game/Rulesets/Mods/ModExtensions.cs index b22030414b..49a3c9178a 100644 --- a/osu.Game/Rulesets/Mods/ModExtensions.cs +++ b/osu.Game/Rulesets/Mods/ModExtensions.cs @@ -21,8 +21,10 @@ namespace osu.Game.Rulesets.Mods { User = new APIUser { - Id = APIUser.SYSTEM_USER_ID, + // TODO: Some fields weren't copied from replayData.User (namely IsBot and Id). Should ModCreatedUser inherit from APIUser so we could pass it verbatim to avoid future mistakes ? + Id = replayData.User.OnlineID, Username = replayData.User.Username, + IsBot = replayData.User.IsBot, } } }; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 1d0499c909..03231f9329 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Ranking if (Score != null) { // only show flair / animation when arriving after watching a play that isn't autoplay. - bool shouldFlair = player != null && Score.Mods.All(m => m.UserPlayable); + bool shouldFlair = player != null && !Score.User.IsBot; ScorePanelList.AddScore(Score, shouldFlair); } From 270e2a66009b7b551837ad39ed45be9d3cdbf3b9 Mon Sep 17 00:00:00 2001 From: Nabile Rahmani Date: Tue, 29 Aug 2023 23:59:08 +0200 Subject: [PATCH 38/52] Update osu.Game/Rulesets/Mods/ModExtensions.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Mods/ModExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModExtensions.cs b/osu.Game/Rulesets/Mods/ModExtensions.cs index 49a3c9178a..aa106f1203 100644 --- a/osu.Game/Rulesets/Mods/ModExtensions.cs +++ b/osu.Game/Rulesets/Mods/ModExtensions.cs @@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Mods { User = new APIUser { - // TODO: Some fields weren't copied from replayData.User (namely IsBot and Id). Should ModCreatedUser inherit from APIUser so we could pass it verbatim to avoid future mistakes ? Id = replayData.User.OnlineID, Username = replayData.User.Username, IsBot = replayData.User.IsBot, From a85f0d57911fd880bb7a406cd5bd82d3934f0b50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Aug 2023 13:29:08 +0900 Subject: [PATCH 39/52] Allow saving changes in tournament system using `Ctrl`+`S` --- osu.Game.Tournament/SaveChangesOverlay.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/SaveChangesOverlay.cs b/osu.Game.Tournament/SaveChangesOverlay.cs index 7838d4ba48..0b5194a51f 100644 --- a/osu.Game.Tournament/SaveChangesOverlay.cs +++ b/osu.Game.Tournament/SaveChangesOverlay.cs @@ -7,13 +7,16 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; using osuTK; namespace osu.Game.Tournament { - internal partial class SaveChangesOverlay : CompositeDrawable + internal partial class SaveChangesOverlay : CompositeDrawable, IKeyBindingHandler { [Resolved] private TournamentGame tournamentGame { get; set; } = null!; @@ -78,6 +81,21 @@ namespace osu.Game.Tournament scheduleNextCheck(); } + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == PlatformAction.Save && !e.Repeat) + { + saveChangesButton.TriggerClick(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + private void scheduleNextCheck() => Scheduler.AddDelayed(() => checkForChanges().FireAndForget(), 1000); private void saveChanges() From 24d6cbefe12b5ac570db353ebaeb2c7e3e192dad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Aug 2023 13:44:59 +0900 Subject: [PATCH 40/52] Remove tournament client minimum window size This seemed like a good idea but people were using it with smaller resolutions, do let's just not do it. Addresses https://github.com/ppy/osu/discussions/24670. --- osu.Game.Tournament/TournamentGame.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index ba3b17b513..9baa448ab8 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -48,8 +48,6 @@ namespace osu.Game.Tournament { frameworkConfig.BindWith(FrameworkSetting.WindowedSize, windowSize); - windowSize.MinValue = new Size(TournamentSceneManager.REQUIRED_WIDTH, TournamentSceneManager.STREAM_AREA_HEIGHT); - windowMode = frameworkConfig.GetBindable(FrameworkSetting.WindowMode); Add(loadingSpinner = new LoadingSpinner(true, true) From 0f123fd8e0be4127405236fec5cb4a114a5893fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Aug 2023 08:20:57 +0200 Subject: [PATCH 41/52] Remove unused using directive --- osu.Game.Tournament/TournamentGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 9baa448ab8..25dc8ae1e5 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Drawing; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; From c91031604c20043e5e9315ef2eff5de6c708344d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 30 Aug 2023 21:41:57 +0300 Subject: [PATCH 42/52] Add test cases for hitting hit circles with "early fade" behaviour --- .../TestSceneHitCircleLateFade.cs | 74 ++++++++++++++++--- 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 3c32b4fa65..149fd61311 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -3,8 +3,10 @@ using System; using System.Linq; +using FFmpeg.AutoGen; using NUnit.Framework; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mods; @@ -13,6 +15,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Tests.Visual; using osuTK; @@ -23,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Tests private float? alphaAtMiss; [Test] - public void TestHitCircleClassicMod() + public void TestHitCircleClassicModMiss() { AddStep("Create hit circle", () => { @@ -61,8 +64,21 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("Transparent when missed", () => alphaAtMiss == 0); } + /// + /// No early fade is expected to be applied if the hit circle has been hit. + /// [Test] - public void TestHitCircleNoMod() + public void TestHitCircleClassicModHit() + { + AddStep("Create hit circle", () => + { + SelectedMods.Value = new Mod[] { new OsuModClassic() }; + createCircle(true); + }); + } + + [Test] + public void TestHitCircleNoModMiss() { AddStep("Create hit circle", () => { @@ -74,6 +90,16 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("Opaque when missed", () => alphaAtMiss == 1); } + [Test] + public void TestHitCircleNoModHit() + { + AddStep("Create hit circle", () => + { + SelectedMods.Value = Array.Empty(); + createCircle(true); + }); + } + [Test] public void TestSliderClassicMod() { @@ -100,24 +126,27 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("Head circle opaque when missed", () => alphaAtMiss == 1); } - private void createCircle() + private void createCircle(bool auto = false) { alphaAtMiss = null; - DrawableHitCircle drawableHitCircle = new DrawableHitCircle(new HitCircle + TestDrawableHitCircle drawableHitCircle = new TestDrawableHitCircle(new HitCircle { StartTime = Time.Current + 500, - Position = new Vector2(250) - }); + Position = new Vector2(250), + }, auto); + + drawableHitCircle.Scale = new Vector2(2f); foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObject(drawableHitCircle); drawableHitCircle.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableHitCircle.OnNewResult += (_, _) => + drawableHitCircle.OnNewResult += (_, result) => { - alphaAtMiss = drawableHitCircle.Alpha; + if (!result.IsHit) + alphaAtMiss = drawableHitCircle.Alpha; }; Child = drawableHitCircle; @@ -138,6 +167,8 @@ namespace osu.Game.Rulesets.Osu.Tests }) }); + drawableSlider.Scale = new Vector2(2f); + drawableSlider.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); drawableSlider.OnLoadComplete += _ => @@ -145,12 +176,35 @@ namespace osu.Game.Rulesets.Osu.Tests foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObject(drawableSlider.HeadCircle); - drawableSlider.HeadCircle.OnNewResult += (_, _) => + drawableSlider.HeadCircle.OnNewResult += (_, result) => { - alphaAtMiss = drawableSlider.HeadCircle.Alpha; + if (!result.IsHit) + alphaAtMiss = drawableSlider.HeadCircle.Alpha; }; }; Child = drawableSlider; } + + protected partial class TestDrawableHitCircle : DrawableHitCircle + { + private readonly bool auto; + + public TestDrawableHitCircle(HitCircle h, bool auto) + : base(h) + { + this.auto = auto; + } + + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + if (auto && !userTriggered && timeOffset >= 0 && CheckHittable?.Invoke(this, Time.Current) != false) + { + // force success + ApplyResult(r => r.Type = HitResult.Great); + } + else + base.CheckForResult(userTriggered, timeOffset); + } + } } } From 993cebe785a07d8c9054a4e7bf6c36567b65ef61 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 30 Aug 2023 21:42:47 +0300 Subject: [PATCH 43/52] Fix mod "Classic" interfering with `DrawableHitCircle` animation while in hit state --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 250d97c537..e740e6d201 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -85,13 +85,16 @@ namespace osu.Game.Rulesets.Osu.Mods private void applyEarlyFading(DrawableHitCircle circle) { - circle.ApplyCustomUpdateState += (o, _) => + circle.ApplyCustomUpdateState += (dho, state) => { - using (o.BeginAbsoluteSequence(o.StateUpdateTime)) + using (dho.BeginAbsoluteSequence(dho.StateUpdateTime)) { - double okWindow = o.HitObject.HitWindows.WindowFor(HitResult.Ok); - double lateMissFadeTime = o.HitObject.HitWindows.WindowFor(HitResult.Meh) - okWindow; - o.Delay(okWindow).FadeOut(lateMissFadeTime); + if (state != ArmedState.Hit) + { + double okWindow = dho.HitObject.HitWindows.WindowFor(HitResult.Ok); + double lateMissFadeTime = dho.HitObject.HitWindows.WindowFor(HitResult.Meh) - okWindow; + dho.Delay(okWindow).FadeOut(lateMissFadeTime); + } } }; } From b8df714f56c162376396efa2ee71a8c14894e276 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 30 Aug 2023 21:50:52 +0300 Subject: [PATCH 44/52] Remove unused using directives --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 149fd61311..1557afbf84 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -3,10 +3,8 @@ using System; using System.Linq; -using FFmpeg.AutoGen; using NUnit.Framework; using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mods; From 2d88135198c245a1185fe904b0d61abbd483319f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 16:18:18 +0900 Subject: [PATCH 45/52] Add automated test coverage for new test --- .../TestSceneHitCircleLateFade.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 1557afbf84..3b051912fd 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -68,11 +68,17 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestHitCircleClassicModHit() { + TestDrawableHitCircle circle = null!; + AddStep("Create hit circle", () => { SelectedMods.Value = new Mod[] { new OsuModClassic() }; - createCircle(true); + circle = createCircle(true); }); + + AddUntilStep("Wait until circle is hit", () => circle.Result?.Type == HitResult.Great); + AddUntilStep("Wait for miss window", () => Clock.CurrentTime, () => Is.GreaterThanOrEqualTo(circle.HitObject.StartTime + circle.HitObject.HitWindows.WindowFor(HitResult.Miss))); + AddAssert("Check circle is still visible", () => circle.Alpha, () => Is.GreaterThan(0)); } [Test] @@ -124,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("Head circle opaque when missed", () => alphaAtMiss == 1); } - private void createCircle(bool auto = false) + private TestDrawableHitCircle createCircle(bool shouldHit = false) { alphaAtMiss = null; @@ -132,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 500, Position = new Vector2(250), - }, auto); + }, shouldHit); drawableHitCircle.Scale = new Vector2(2f); @@ -148,6 +154,8 @@ namespace osu.Game.Rulesets.Osu.Tests }; Child = drawableHitCircle; + + return drawableHitCircle; } private void createSlider() @@ -180,22 +188,23 @@ namespace osu.Game.Rulesets.Osu.Tests alphaAtMiss = drawableSlider.HeadCircle.Alpha; }; }; + Child = drawableSlider; } protected partial class TestDrawableHitCircle : DrawableHitCircle { - private readonly bool auto; + private readonly bool shouldHit; - public TestDrawableHitCircle(HitCircle h, bool auto) + public TestDrawableHitCircle(HitCircle h, bool shouldHit) : base(h) { - this.auto = auto; + this.shouldHit = shouldHit; } protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (auto && !userTriggered && timeOffset >= 0 && CheckHittable?.Invoke(this, Time.Current) != false) + if (shouldHit && !userTriggered && timeOffset >= 0 && CheckHittable?.Invoke(this, Time.Current) != false) { // force success ApplyResult(r => r.Type = HitResult.Great); From 5fa31b7b359ca15a5a0cde035df2279a22f695d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 18:05:34 +0900 Subject: [PATCH 46/52] Fix schedule screen not responding to new matches being added --- .../Screens/Schedule/ScheduleScreen.cs | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 063c231add..ee4b762be3 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -2,6 +2,7 @@ // 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.Bindables; @@ -19,6 +20,7 @@ namespace osu.Game.Tournament.Screens.Schedule { public partial class ScheduleScreen : TournamentScreen { + private readonly BindableList allMatches = new BindableList(); private readonly Bindable currentMatch = new Bindable(); private Container mainContainer = null!; private LadderInfo ladder = null!; @@ -101,16 +103,23 @@ namespace osu.Game.Tournament.Screens.Schedule { base.LoadComplete(); + allMatches.BindTo(ladder.Matches); + allMatches.BindCollectionChanged((_, _) => refresh()); + currentMatch.BindTo(ladder.CurrentMatch); - currentMatch.BindValueChanged(matchChanged, true); + currentMatch.BindValueChanged(_ => refresh(), true); } - private void matchChanged(ValueChangedEvent match) + private void refresh() { - var upcoming = ladder.Matches.Where(p => !p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4); - var conditionals = ladder - .Matches.Where(p => !p.Completed.Value && (p.Team1.Value == null || p.Team2.Value == null) && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) - .SelectMany(m => m.ConditionalMatches.Where(cp => m.Acronyms.TrueForAll(a => cp.Acronyms.Contains(a)))); + IEnumerable upcoming = + allMatches + .Where(p => !p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4); + + IEnumerable conditionals = + allMatches + .Where(p => !p.Completed.Value && (p.Team1.Value == null || p.Team2.Value == null) && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) + .SelectMany(m => m.ConditionalMatches.Where(cp => m.Acronyms.TrueForAll(a => cp.Acronyms.Contains(a)))); upcoming = upcoming.Concat(conditionals); upcoming = upcoming.OrderBy(p => p.Date.Value).Take(8); @@ -137,12 +146,12 @@ namespace osu.Game.Tournament.Screens.Schedule { RelativeSizeAxes = Axes.Both, Width = 0.4f, - ChildrenEnumerable = ladder.Matches - .Where(p => p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null - && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) - .OrderByDescending(p => p.Date.Value) - .Take(8) - .Select(p => new ScheduleMatch(p)) + ChildrenEnumerable = allMatches + .Where(p => p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null + && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) + .OrderByDescending(p => p.Date.Value) + .Take(8) + .Select(p => new ScheduleMatch(p)) }, new ScheduleContainer("upcoming matches") { @@ -161,7 +170,7 @@ namespace osu.Game.Tournament.Screens.Schedule } }; - if (match.NewValue != null) + if (currentMatch.Value != null) { comingUpNext.Child = new FillFlowContainer { @@ -170,12 +179,12 @@ namespace osu.Game.Tournament.Screens.Schedule Spacing = new Vector2(30), Children = new Drawable[] { - new ScheduleMatch(match.NewValue, false) + new ScheduleMatch(currentMatch.Value, false) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - new TournamentSpriteTextWithBackground(match.NewValue.Round.Value?.Name.Value ?? string.Empty) + new TournamentSpriteTextWithBackground(currentMatch.Value.Round.Value?.Name.Value ?? string.Empty) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -185,7 +194,7 @@ namespace osu.Game.Tournament.Screens.Schedule { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = match.NewValue.Team1.Value?.FullName + " vs " + match.NewValue.Team2.Value?.FullName, + Text = currentMatch.Value.Team1.Value?.FullName + " vs " + currentMatch.Value.Team2.Value?.FullName, Font = OsuFont.Torus.With(size: 24, weight: FontWeight.SemiBold) }, new FillFlowContainer @@ -196,7 +205,7 @@ namespace osu.Game.Tournament.Screens.Schedule Origin = Anchor.CentreLeft, Children = new Drawable[] { - new ScheduleMatchDate(match.NewValue.Date.Value) + new ScheduleMatchDate(currentMatch.Value.Date.Value) { Font = OsuFont.Torus.With(size: 24, weight: FontWeight.Regular) } From 7aa1505062cd3d15082d3e10dbf9cc232048b6c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 18:13:36 +0900 Subject: [PATCH 47/52] Improve legibility of LINQ queries for recent/upcoming matches --- .../Screens/Schedule/ScheduleScreen.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index ee4b762be3..62c54daa75 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -112,17 +112,25 @@ namespace osu.Game.Tournament.Screens.Schedule private void refresh() { - IEnumerable upcoming = - allMatches - .Where(p => !p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4); + const int days_for_displays = 4; IEnumerable conditionals = allMatches - .Where(p => !p.Completed.Value && (p.Team1.Value == null || p.Team2.Value == null) && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) + .Where(m => !m.Completed.Value && (m.Team1.Value == null || m.Team2.Value == null) && Math.Abs(m.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < days_for_displays) .SelectMany(m => m.ConditionalMatches.Where(cp => m.Acronyms.TrueForAll(a => cp.Acronyms.Contains(a)))); - upcoming = upcoming.Concat(conditionals); - upcoming = upcoming.OrderBy(p => p.Date.Value).Take(8); + IEnumerable upcoming = + allMatches + .Where(m => !m.Completed.Value && m.Team1.Value != null && m.Team2.Value != null && Math.Abs(m.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < days_for_displays) + .Concat(conditionals) + .OrderBy(m => m.Date.Value) + .Take(8); + + var recent = + allMatches + .Where(m => m.Completed.Value && m.Team1.Value != null && m.Team2.Value != null && Math.Abs(m.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < days_for_displays) + .OrderByDescending(m => m.Date.Value) + .Take(8); ScheduleContainer comingUpNext; @@ -146,12 +154,7 @@ namespace osu.Game.Tournament.Screens.Schedule { RelativeSizeAxes = Axes.Both, Width = 0.4f, - ChildrenEnumerable = allMatches - .Where(p => p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null - && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) - .OrderByDescending(p => p.Date.Value) - .Take(8) - .Select(p => new ScheduleMatch(p)) + ChildrenEnumerable = recent.Select(p => new ScheduleMatch(p)) }, new ScheduleContainer("upcoming matches") { From 4b68493084a4f9b87bbead6fdb02367d5d062832 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 18:05:18 +0900 Subject: [PATCH 48/52] Add ability to test recent / upcoming matches on schedule screen --- .../Screens/TestSceneScheduleScreen.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs index f3c3fdec97..a58f09d13a 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs @@ -12,6 +12,13 @@ namespace osu.Game.Tournament.Tests.Screens { public partial class TestSceneScheduleScreen : TournamentScreenTestScene { + public override void SetUpSteps() + { + AddStep("clear matches", () => Ladder.Matches.Clear()); + + base.SetUpSteps(); + } + [BackgroundDependencyLoader] private void load() { @@ -34,6 +41,36 @@ namespace osu.Game.Tournament.Tests.Screens AddStep("Set null current match", () => Ladder.CurrentMatch.Value = null); } + [Test] + public void TestUpcomingMatches() + { + AddStep("Add upcoming match", () => + { + var tournamentMatch = CreateSampleMatch(); + + tournamentMatch.Date.Value = DateTimeOffset.UtcNow.AddMinutes(5); + tournamentMatch.Completed.Value = false; + + Ladder.Matches.Add(tournamentMatch); + }); + } + + [Test] + public void TestRecentMatches() + { + AddStep("Add recent match", () => + { + var tournamentMatch = CreateSampleMatch(); + + tournamentMatch.Date.Value = DateTimeOffset.UtcNow; + tournamentMatch.Completed.Value = true; + tournamentMatch.Team1Score.Value = tournamentMatch.PointsToWin; + tournamentMatch.Team2Score.Value = tournamentMatch.PointsToWin / 2; + + Ladder.Matches.Add(tournamentMatch); + }); + } + private void setMatchDate(TimeSpan relativeTime) // Humanizer cannot handle negative timespans. => AddStep($"start time is {relativeTime}", () => From 540c58c359ed47ee758519da0b089621be41b7cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 18:46:47 +0900 Subject: [PATCH 49/52] Fix tournament screen tests not matching `OsuGameBase` resolution This would cause things to look completely out of whack. The numbers here match `DrawSizePreservingFillContainer` defaults as used by `OsuGameBase.CreateScalingContainer()`. --- osu.Game.Tournament.Tests/TournamentScreenTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/TournamentScreenTestScene.cs b/osu.Game.Tournament.Tests/TournamentScreenTestScene.cs index 8adffe1468..e8cca00c92 100644 --- a/osu.Game.Tournament.Tests/TournamentScreenTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentScreenTestScene.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tournament.Tests { public TournamentScalingContainer() { - TargetDrawSize = new Vector2(1920, 1080); + TargetDrawSize = new Vector2(1024, 768); RelativeSizeAxes = Axes.Both; } From 4bdaca3816ad8cf6ddd3125fce83c5c11e975a20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 18:05:04 +0900 Subject: [PATCH 50/52] Fix regression in spacing on schedule screen --- osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 62c54daa75..d02559d6b7 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -294,6 +294,7 @@ namespace osu.Game.Tournament.Screens.Schedule { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.Both, + Spacing = new Vector2(0, -6), Margin = new MarginPadding(10) }, } From 34b279845bf4d4569b8cc9897cba0b58e4219c20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 19:03:56 +0900 Subject: [PATCH 51/52] Add more testability for song bar --- osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs index e0444b6126..95d6b6d107 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs @@ -58,9 +58,14 @@ namespace osu.Game.Tournament.Tests.Components songBar.Beatmap = new TournamentBeatmap(beatmap); }); + AddStep("set mods to HR", () => songBar.Mods = LegacyMods.HardRock); AddStep("set mods to DT", () => songBar.Mods = LegacyMods.DoubleTime); AddStep("unset mods", () => songBar.Mods = LegacyMods.None); + + AddToggleStep("toggle expanded", expanded => songBar.Expanded = expanded); + + AddStep("set null beatmap", () => songBar.Beatmap = null); } } } From cf9c8120c5292774b8aec7d2b0282003cdca32d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2023 19:04:06 +0900 Subject: [PATCH 52/52] Fix potential race condition in song bar beatmap lookup flow Cancelling a web request may not necessarily cancel the callbacks. This might help with https://github.com/ppy/osu/issues/24598. --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index bf835122cc..5407c21079 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -92,8 +92,16 @@ namespace osu.Game.Tournament.IPC else { beatmapLookupRequest = new GetBeatmapRequest(new APIBeatmap { OnlineID = beatmapId }); - beatmapLookupRequest.Success += b => Beatmap.Value = new TournamentBeatmap(b); - beatmapLookupRequest.Failure += _ => Beatmap.Value = null; + beatmapLookupRequest.Success += b => + { + if (lastBeatmapId == beatmapId) + Beatmap.Value = new TournamentBeatmap(b); + }; + beatmapLookupRequest.Failure += _ => + { + if (lastBeatmapId == beatmapId) + Beatmap.Value = null; + }; API.Queue(beatmapLookupRequest); } }